This commit is contained in:
Денис Катаев 2025-01-31 22:48:00 +00:00 committed by Daniil Chemerkin
parent f3357ee02a
commit 2cf6e0c842
7 changed files with 420 additions and 35 deletions

16
package-lock.json generated
View File

@ -36,6 +36,7 @@
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.2",
"react-slick": "^0.30.2",
"react-webcam": "^7.2.0",
"sass": "^1.77.6",
"slick-carousel": "^1.8.1",
"socket.io-client": "^4.8.1",
@ -4681,6 +4682,15 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react-webcam": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-webcam/-/react-webcam-7.2.0.tgz",
"integrity": "sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==",
"peerDependencies": {
"react": ">=16.2.0",
"react-dom": ">=16.2.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@ -8641,6 +8651,12 @@
"prop-types": "^15.6.2"
}
},
"react-webcam": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-webcam/-/react-webcam-7.2.0.tgz",
"integrity": "sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==",
"requires": {}
},
"readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",

View File

@ -43,6 +43,7 @@
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.2",
"react-slick": "^0.30.2",
"react-webcam": "^7.2.0",
"sass": "^1.77.6",
"slick-carousel": "^1.8.1",
"socket.io-client": "^4.8.1",

View File

@ -52,7 +52,7 @@ import HomePage from "../HomePage";
import UserCallbacksPage from "../UserCallbacksPage";
import NavbarFooter, { INavbarHomeItems } from "../NavbarFooter";
import { EPathsFromHome } from "@/store/siteConfig";
import parseAPNG, { APNG } from "apng-js";
import { APNG } from "apng-js";
import { useApi, useApiCall } from "@/api";
import { Asset } from "@/api/resources/Assets";
import PaymentResultPage from "../PaymentPage/results";
@ -145,6 +145,7 @@ if (isProduction) {
function App(): JSX.Element {
const location = useLocation();
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
setLeoApng
useScrollToTop({ scrollBehavior: "auto" });
// const [
// padLockApng,
@ -232,6 +233,7 @@ function App(): JSX.Element {
}, [api]);
const { data } = useApiCall<Asset[]>(assetsData);
data
// jwt auth
useLayoutEffect(() => {
@ -275,17 +277,17 @@ function App(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, api, token]);
useEffect(() => {
async function getApng() {
if (!data) return;
const response = await fetch(
data.find((item) => item.key === "au.apng.leo")?.url || ""
);
const arrayBuffer = await response.arrayBuffer();
setLeoApng(parseAPNG(arrayBuffer));
}
getApng();
}, [data]);
// useEffect(() => {
// async function getApng() {
// if (!data) return;
// const response = await fetch(
// data.find((item) => item.key === "au.apng.leo")?.url || ""
// );
// const arrayBuffer = await response.arrayBuffer();
// setLeoApng(parseAPNG(arrayBuffer));
// }
// getApng();
// }, [data]);
// useEffect(() => {
// (async () => {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100dvh;
min-height: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
}
// .modal {
// width: 100vw !important;
// height: 100dvh !important;
// margin: 0 !important;
// padding: 0 !important;
// max-width: none !important;
// background: transparent !important;
// }
.cameraContainer {
width: 100%;
height: 100dvh;
position: relative;
overflow: hidden;
}
.camera {
width: 100%;
height: 100dvh;
object-fit: cover;
}
.shutterButton {
background: var(--white);
border-radius: 50%;
bottom: calc(0dvh + 30px);
left: 50%;
padding: 15px;
border: 20px solid hsla(0, 0%, 100%, 0.28);
position: absolute;
-webkit-transform: translate(-50%, 15px);
transform: translate(-50%, 15px);
}
.handIcon {
position: absolute;
height: calc(100dvh - 120px);
left: 50%;
top: 20px;
transform: translate(-50%);
width: auto;
z-index: 9;
}

View File

@ -1,4 +1,3 @@
import PalmCameraModal from "@/components/palmistry/palm-camera-modal/palm-camera-modal";
import styles from "./styles.module.scss";
import { DataURIToBlob } from "@/services/data";
import { useApi } from "@/api";
@ -14,6 +13,7 @@ import UploadModal from "@/components/palmistry/upload-modal/upload-modal";
import Toast from "@/components/pages/ABDesign/v1/components/Toast";
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import CameraModal from "../../components/CameraModal";
const isProduction = import.meta.env.MODE === "production";
@ -30,6 +30,7 @@ function Camera() {
const [isLoading, setIsLoading] = useState(false);
const [uploadMenuModalIsOpen, setUploadMenuModalIsOpen] = useState(false);
const [toastVisible, setToastVisible] = useState<EToastVisible | null>(null);
const [test, setTest] = useState<string | null>(null);
const handleNext = () => {
navigate(routes.client.palmistryV1ScannedPhoto());
@ -77,6 +78,13 @@ function Camera() {
const getLines = async (file: File | Blob) => {
setIsLoading(true);
// Добавляем логирование размера файла
console.log('Размер файла перед отправкой:', {
bytesSize: file.size,
megabytesSize: (file.size / (1024 * 1024)).toFixed(2) + ' MB'
});
const formData = new FormData();
formData.append("file", file);
try {
@ -105,15 +113,38 @@ function Camera() {
const onTakePhoto = async (photo: string) => {
setUploadMenuModalIsOpen(false);
const file = DataURIToBlob(photo);
const result = await getLines(file);
dispatch(
actions.palmistry.update({
photo,
})
);
if (!checkPalmistryLines(result?.lines || [])) return;
handleNext();
console.log('Начало обработки фото');
try {
console.log('Попытка конвертации base64 в Blob');
const file = DataURIToBlob(photo);
console.log('Размер Blob:', {
size: file.size,
megabytes: (file.size / (1024 * 1024)).toFixed(2) + ' MB'
});
const result = await getLines(file);
// Очищаем большие переменные
URL.revokeObjectURL(URL.createObjectURL(file));
dispatch(
actions.palmistry.update({
photo,
})
);
if (!checkPalmistryLines(result?.lines || [])) return;
handleNext();
} catch (error) {
console.error('Ошибка при обработке фото:', error);
} finally {
// Принудительный запуск сборщика мусора (не гарантировано, но может помочь)
if (window.gc) {
window.gc();
}
}
};
const onSelectFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
@ -155,10 +186,20 @@ function Camera() {
Upload
</button>
)}
{test && <div>{test}</div>}
{!isLoading && !uploadMenuModalIsOpen && (
<PalmCameraModal
// <PalmCameraModal
// onClose={() => console.log("close")}
// onTakePhoto={onTakePhoto}
// />
<CameraModal
onClose={() => console.log("close")}
onTakePhoto={onTakePhoto}
onError={(error) => {
console.error(error)
setTest(JSON.stringify(error))
setToastVisible(EToastVisible.try_again)
}}
/>
)}
{isLoading && (

View File

@ -12,6 +12,28 @@ type Props = {
export default function PalmCameraModal(props: Props) {
const videoEl = React.useRef<HTMLVideoElement | null>(null);
const [isVideoReady, setIsVideoReady] = React.useState(false);
React.useEffect(() => {
const video = videoEl.current;
if (!video) return;
const handleVideoReady = () => {
console.log('Метаданные видео загружены:', {
videoWidth: videoEl.current?.videoWidth,
videoHeight: videoEl.current?.videoHeight,
readyState: videoEl.current?.readyState
});
setIsVideoReady(true);
video.play();
};
video.addEventListener("loadedmetadata", handleVideoReady);
return () => {
video.removeEventListener("loadedmetadata", handleVideoReady);
};
}, []);
const [mediaStream, setMediaStream] = React.useState<MediaStream | null>(
null
@ -31,17 +53,26 @@ export default function PalmCameraModal(props: Props) {
video: { facingMode: { ideal: "environment" } },
});
console.log('Медиа поток получен:', {
tracks: stream.getTracks().map(track => ({
kind: track.kind,
settings: track.getSettings()
}))
});
setMediaStream(stream);
videoEl.current.srcObject = stream;
videoEl.current.addEventListener("loadedmetadata", videoEl.current.play);
// videoEl.current.addEventListener("loadedmetadata", videoEl.current.play);
} catch (error) {
console.error("Camera is not available", error);
}
};
const deactivateCamera = React.useCallback(() => {
if (!mediaStream) return;
mediaStream?.getTracks().forEach((track) => track.stop());
setMediaStream(null);
}, [mediaStream]);
React.useEffect(() => {
@ -51,29 +82,122 @@ export default function PalmCameraModal(props: Props) {
React.useEffect(() => {
return () => {
deactivateCamera();
if (videoEl.current) {
videoEl.current.srcObject = null;
videoEl.current.load();
}
};
}, [deactivateCamera]);
const takePhoto = () => {
console.log('Начало захвата фото');
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context || !videoEl.current) return null;
// Функция очистки ресурсов
const cleanup = () => {
// Очищаем canvas
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
// Удаляем canvas
canvas.width = 0;
canvas.height = 0;
canvas.remove();
};
const width = videoEl.current.videoWidth;
const height = videoEl.current.videoHeight;
try {
if (!context || !videoEl.current) {
console.error('Контекст canvas или видео не доступны');
return null;
};
canvas.width = width;
canvas.height = height;
context.drawImage(videoEl.current, 0, 0, width, height);
const width = videoEl.current.videoWidth;
const height = videoEl.current.videoHeight;
const data = canvas.toDataURL("image/png");
console.log('Размеры видео:', {
width,
height,
estimatedMemoryUsage: (width * height * 4 / (1024 * 1024)).toFixed(2) + ' MB' // 4 байта на пиксель
});
return data;
console.log('Параметры видео перед захватом:', {
videoWidth: width,
videoHeight: height,
videoReady: isVideoReady,
videoCurrentTime: videoEl.current.currentTime,
readyState: videoEl.current.readyState
});
if (!width || !height) {
console.log("Video is not ready");
return;
}
// Попробуем уменьшить размер, если он слишком большой
// const MAX_DIMENSION = 1920; // максимальный размер стороны
// let targetWidth = width;
// let targetHeight = height;
// if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
// if (width > height) {
// targetWidth = MAX_DIMENSION;
// targetHeight = Math.round(height * (MAX_DIMENSION / width));
// } else {
// targetHeight = MAX_DIMENSION;
// targetWidth = Math.round(width * (MAX_DIMENSION / height));
// }
// }
canvas.width = width;
canvas.height = height;
console.log('Попытка отрисовки на canvas');
context.drawImage(videoEl.current, 0, 0, width, height);
console.log('Попытка конвертации в base64');
const data = canvas.toDataURL("image/png");
cleanup();
console.log('Размер base64:', {
length: data.length,
megabytes: (data.length / (1024 * 1024)).toFixed(2) + ' MB'
});
console.log('Результат захвата:', {
dataLength: data.length,
isEmptyCapture: data === 'data:,',
canvasWidth: canvas.width,
canvasHeight: canvas.height
});
if (data === "data:,") {
console.log("Couldn't get a photo");
return;
}
return data;
} catch (error) {
console.error('Ошибка при обработке фото:', error);
cleanup();
return null;
}
};
const onClickTakePhoto = () => {
const onClickTakePhoto = React.useCallback(() => {
console.log('Попытка сделать фото:', {
isVideoReady,
videoElement: {
width: videoEl.current?.videoWidth,
height: videoEl.current?.videoHeight,
readyState: videoEl.current?.readyState
}
});
if (!isVideoReady) {
console.warn('Видео не готово для захвата');
return;
};
const photo = takePhoto();
deactivateCamera();
@ -81,7 +205,7 @@ export default function PalmCameraModal(props: Props) {
if (!photo) return;
props.onTakePhoto(photo);
};
}, [isVideoReady]);
return (
<ModalOverlay
@ -152,6 +276,9 @@ export default function PalmCameraModal(props: Props) {
<div
className="palm-camera-modal__button-shutter"
onClick={onClickTakePhoto}
style={{
opacity: isVideoReady ? 1 : 0.5,
}}
/>
</div>
</Modal>