Develop
This commit is contained in:
parent
f3357ee02a
commit
2cf6e0c842
16
package-lock.json
generated
16
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
144
src/components/PalmistryV1/components/CameraModal/index.tsx
Normal file
144
src/components/PalmistryV1/components/CameraModal/index.tsx
Normal file
File diff suppressed because one or more lines are too long
@ -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;
|
||||
}
|
||||
@ -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 && (
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user