diff --git a/app/src/app/camera/page.tsx b/app/src/app/camera/page.tsx
index de2a027..e348fc9 100644
--- a/app/src/app/camera/page.tsx
+++ b/app/src/app/camera/page.tsx
@@ -1,14 +1,14 @@
"use client";
import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -26,421 +26,457 @@ import { Toaster } from "@/components/ui/sonner";
import { todayAssignment } from "@/types";
import { Answered } from "@/components/Answered";
import { AssignmentBadge } from "@/components/AssignmentBadge";
+import imageCompression from "browser-image-compression";
interface ImagePreviewProps {
- image: string | null;
- onClick: () => void;
+ image: string | null;
+ onClick: () => void;
}
interface UploadResponse {
- url: string;
- success: boolean;
+ url: string;
+ success: boolean;
}
interface ScoreData {
- similarity: number;
- answerTime: Date;
- imageUrl: string;
- assignmentId: number;
- userId: number;
+ similarity: number;
+ answerTime: Date;
+ imageUrl: string;
+ assignmentId: number;
+ userId: number;
}
const BUCKET_NAME = "kz2404";
const ImagePreview = ({ image, onClick }: ImagePreviewProps) => (
-
{
- if (e.key === "Enter" || e.key === " ") {
- onClick();
- }
- }}
- />
+
{
+ if (e.key === "Enter" || e.key === " ") {
+ onClick();
+ }
+ }}
+ />
);
const DialogImagePreview = ({ image }: { image: string | null }) => {
- if (!image) return null;
-
- return (
-
- );
+ if (!image) return null;
+
+ return (
+
+ );
};
const LoadingSpinner = () => (
-
+
);
const imageDataToBase64 = (imageData: ImageData): string => {
- const canvas = document.createElement("canvas");
- canvas.width = imageData.width;
- canvas.height = imageData.height;
+ const canvas = document.createElement("canvas");
+ canvas.width = imageData.width;
+ canvas.height = imageData.height;
- const ctx = canvas.getContext("2d");
- if (!ctx) throw new Error("Failed to get 2D context");
+ const ctx = canvas.getContext("2d");
+ if (!ctx) throw new Error("Failed to get 2D context");
- ctx.putImageData(imageData, 0, 0);
- return canvas.toDataURL("image/jpeg");
+ ctx.putImageData(imageData, 0, 0);
+ return canvas.toDataURL("image/jpeg");
};
const CameraApp = () => {
- const [image, setImage] = useState
(null);
- const [showImage, setShowImage] = useState(false);
- const [isUploading, setIsUploading] = useState(false);
- const [showConfirmDialog, setShowConfirmDialog] = useState(false);
- const [tempImage, setTempImage] = useState(null);
- const camera = useRef(null);
- const [devices, setDevices] = useState([]);
- const [activeDeviceId, setActiveDeviceId] = useState(undefined);
- const [currentDeviceIndex, setCurrentDeviceIndex] = useState(0);
- const [todayAssignment, setTodayAssignment] = useState();
- const [assignments, setAssignments] = useState([]);
- const [isActive, setIsActive] = useState(true);
-
- useEffect(() => {
- const getDevices = async () => {
- const user = localStorage.getItem("userID");
- if (user === null) {
- console.error("ユーザー情報が取得できませんでした。");
- return;
- }
- const userInfo = JSON.parse(user);
- const resAssignment = await fetch(`/api/assignment/today?uid=${userInfo?.uid}`);
- const assignmentData = await resAssignment.json();
-
- if (assignmentData.length === 0) {
- setIsActive(false);
- return;
- }
-
- const isAnsweredAll = assignmentData.every(
- (assignment: todayAssignment) => assignment.isAnswered,
- );
- if (isAnsweredAll) {
- setIsActive(false);
- return;
- }
-
- const notAnsweredAssignment = assignmentData.find(
- (assignment: todayAssignment) => !assignment.isAnswered,
- );
-
- setTodayAssignment(notAnsweredAssignment);
- setAssignments(assignmentData);
-
- if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
- console.error("メディアデバイスAPIがサポートされていません。");
- }
-
- try {
- const devices = await navigator.mediaDevices.enumerateDevices();
- const videoDevices = devices.filter((device) => device.kind === "videoinput");
- setDevices(videoDevices);
- if (videoDevices.length > 0) {
- setActiveDeviceId(videoDevices[0].deviceId);
- }
- } catch (error) {
- console.error("デバイスの取得中にエラーが発生しました:", error);
- }
- };
-
- getDevices();
- }, []);
-
- const switchCamera = () => {
- if (devices.length > 1) {
- const nextIndex = (currentDeviceIndex + 1) % devices.length;
- setActiveDeviceId(devices[nextIndex].deviceId);
- setCurrentDeviceIndex(nextIndex);
- }
- };
-
- const uploadImage = async (
- imageData: string,
- ): Promise<{ imageName: string; data: UploadResponse }> => {
- setIsUploading(true);
- try {
- const base64Response = await fetch(imageData);
- const blob = await base64Response.blob();
-
- // 拡張子取得
- const Extension = blob.type.split("/")[1];
-
- // 日付取得
- const date = new Date();
- const thisMonth = date.getMonth() + 1;
- const month = thisMonth < 10 ? "0" + thisMonth : thisMonth;
- const day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
- const formattedDate = `${date.getFullYear()}${month}${day}`;
-
- // ランダム文字列を生成する関数
- const generateRandomString = (charCount = 7): string => {
- const str = Math.random().toString(36).substring(2).slice(-charCount);
- return str.length < charCount ? str + "a".repeat(charCount - str.length) : str;
- };
-
- const randomStr = generateRandomString();
- // ファイル名作成
- const imageName = `${formattedDate}_${randomStr}.${Extension}`;
-
- const formData = new FormData();
- formData.append("image", blob, imageName);
-
- const response = await fetch("/api/minio", {
- method: "POST",
- body: formData,
- });
-
- const data = await response.json();
-
- return { imageName, data };
- } catch (error) {
- console.error("画像のアップロードに失敗しました:", error);
- throw error;
- }
- };
-
- const getCaption = async (imageName: string): Promise<{ caption: string }> => {
- try {
- const response = await fetch(`/api/image?imageName=${imageName}`);
- if (!response.ok) {
- throw new Error("キャプションの取得に失敗しました");
- }
-
- return await response.json();
- } catch (error) {
- console.error("キャプションの取得に失敗しました:", error);
- throw error;
- }
- };
-
- // スコア計算を行います。
- const similarityRequest = async (caption: string) => {
- const words: string[] = shapeCaption(caption);
- const assignmentWord: string = todayAssignment?.english || "";
- const resSimilarity = await postSimilarity(assignmentWord, words);
- return {
- similarity: resSimilarity.similarity as number,
- assignmentId: todayAssignment?.assignmentId as number,
- };
- };
-
- // userIdの取得
- const getUserId = async () => {
- const userString = localStorage.getItem("userID");
- if (userString === null) {
- return null;
- }
- const userData = JSON.parse(userString);
-
- const resUserId = await fetch("/api/user?uid=" + userData.uid, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
- return await resUserId.json();
- };
-
- // scoreの送信
- const submitScore = async (scoreData: ScoreData) => {
- const response = await fetch("/api/score", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ scoreData }),
- });
-
- if (!response.ok) {
- console.error("スコアの送信に失敗しました", response.statusText);
- return;
- }
-
- return await response.json();
- };
-
- const handleConfirm = async () => {
- if (tempImage) {
- try {
- const { imageName } = await uploadImage(tempImage);
- const imageURL = `${process.env.NEXT_PUBLIC_MINIO_ENDPOINT}${BUCKET_NAME}/${imageName}`;
-
- const res = await getCaption(imageName);
- const caption = res.caption;
-
- setShowConfirmDialog(false);
- setImage(tempImage);
- setShowImage(true);
- setTempImage(null);
-
- const { similarity, assignmentId } = await similarityRequest(caption);
-
- const user = await getUserId();
- const userId: number = user.id;
-
- const scoreData: ScoreData = {
- similarity: similarity,
- answerTime: new Date(),
- imageUrl: imageURL,
- assignmentId: assignmentId,
- userId: userId,
- };
- const response = await submitScore(scoreData);
- const score = response.score;
- const percentSimilarity = Math.floor(similarity * 100);
- const message = `${caption} 類似度 ${percentSimilarity}% スコア: ${score.point} ランキングから順位を確認しましょう!`;
- const newAssignments = assignments.map((assignment) => {
- if (assignment.assignmentId === assignmentId) {
- assignment.isAnswered = true;
- }
- return assignment;
- });
- const notAnsweredAssignment = newAssignments.find(
- (assignment: todayAssignment) => !assignment.isAnswered,
- );
-
- setTodayAssignment(notAnsweredAssignment);
-
- setIsUploading(false);
- toast(message);
- setAssignments(newAssignments);
-
- if (newAssignments.every((assignment) => assignment.isAnswered)) {
- setIsActive(false);
- }
-
-
- } catch (error) {
- setIsUploading(false);
- console.error("アップロード中にエラーが発生しました:", error);
- }
- }
- };
-
- const handleCancel = () => {
- setShowConfirmDialog(false);
- setTempImage(null);
- };
-
- const handleImageCapture = (capturedImage: string | ImageData) => {
- const imageStr =
- capturedImage instanceof ImageData ? imageDataToBase64(capturedImage) : capturedImage;
-
- setTempImage(imageStr);
- setShowConfirmDialog(true);
- };
-
- return (
- <>
- {isActive ? (
- <>
-
-
-
-
-
-
-
{
- const file = event.target.files?.[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = () => {
- handleImageCapture(reader.result as string);
- };
- reader.readAsDataURL(file);
- }
- }}
- />
-
-
-
-
-
-
-
-
- 画像のアップロード確認
-
- この画像をアップロードしてもよろしいですか?
-
-
-
-
-
-
- いいえ
- はい
-
-
-
- {isUploading && }
-
- {todayAssignment?.english && (
-
- )}
- >
- ) : (
-
- )}
- >
- );
+ const [image, setImage] = useState(null);
+ const [showImage, setShowImage] = useState(false);
+ const [isUploading, setIsUploading] = useState(false);
+ const [showConfirmDialog, setShowConfirmDialog] = useState(false);
+ const [tempImage, setTempImage] = useState(null);
+ const camera = useRef(null);
+ const [devices, setDevices] = useState([]);
+ const [activeDeviceId, setActiveDeviceId] = useState(
+ undefined
+ );
+ const [currentDeviceIndex, setCurrentDeviceIndex] = useState(0);
+ const [todayAssignment, setTodayAssignment] = useState<
+ todayAssignment | undefined
+ >();
+ const [assignments, setAssignments] = useState([]);
+ const [isActive, setIsActive] = useState(true);
+
+ useEffect(() => {
+ const getDevices = async () => {
+ const user = localStorage.getItem("userID");
+ if (user === null) {
+ console.error("ユーザー情報が取得できませんでした。");
+ return;
+ }
+ const userInfo = JSON.parse(user);
+ const resAssignment = await fetch(
+ `/api/assignment/today?uid=${userInfo?.uid}`
+ );
+ const assignmentData = await resAssignment.json();
+
+ if (assignmentData.length === 0) {
+ setIsActive(false);
+ return;
+ }
+
+ const isAnsweredAll = assignmentData.every(
+ (assignment: todayAssignment) => assignment.isAnswered
+ );
+ if (isAnsweredAll) {
+ setIsActive(false);
+ return;
+ }
+
+ const notAnsweredAssignment = assignmentData.find(
+ (assignment: todayAssignment) => !assignment.isAnswered
+ );
+
+ setTodayAssignment(notAnsweredAssignment);
+ setAssignments(assignmentData);
+
+ if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
+ console.error("メディアデバイスAPIがサポートされていません。");
+ }
+
+ try {
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ const videoDevices = devices.filter(
+ (device) => device.kind === "videoinput"
+ );
+ setDevices(videoDevices);
+ if (videoDevices.length > 0) {
+ setActiveDeviceId(videoDevices[0].deviceId);
+ }
+ } catch (error) {
+ console.error("デバイスの取得中にエラーが発生しました:", error);
+ }
+ };
+
+ getDevices();
+ }, []);
+
+ const switchCamera = () => {
+ if (devices.length > 1) {
+ const nextIndex = (currentDeviceIndex + 1) % devices.length;
+ setActiveDeviceId(devices[nextIndex].deviceId);
+ setCurrentDeviceIndex(nextIndex);
+ }
+ };
+
+ const uploadImage = async (
+ imageData: string
+ ): Promise<{ imageName: string; data: UploadResponse }> => {
+ setIsUploading(true);
+ try {
+ const base64Response = await fetch(imageData);
+ const originalBlob = await base64Response.blob();
+
+ const compressOptions = {
+ maxSizeMB: 0.01,
+ maxWidthOrHeight: 1920,
+ useWebWorker: true,
+ };
+
+ const originalFile = new File([originalBlob], "tempImage", {
+ type: originalBlob.type,
+ });
+ const compressedBlob = await imageCompression(
+ originalFile,
+ compressOptions
+ );
+
+ // 拡張子取得
+ const Extension = compressedBlob.type.split("/")[1];
+
+ // 日付取得
+ const date = new Date();
+ const thisMonth = date.getMonth() + 1;
+ const month = thisMonth < 10 ? "0" + thisMonth : thisMonth;
+ const day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
+ const formattedDate = `${date.getFullYear()}${month}${day}`;
+
+ // ランダム文字列を生成する関数
+ const generateRandomString = (charCount = 7): string => {
+ const str = Math.random().toString(36).substring(2).slice(-charCount);
+ return str.length < charCount
+ ? str + "a".repeat(charCount - str.length)
+ : str;
+ };
+
+ const randomStr = generateRandomString();
+ // ファイル名作成
+ const imageName = `${formattedDate}_${randomStr}.${Extension}`;
+
+ const formData = new FormData();
+ formData.append("image", compressedBlob, imageName);
+
+ const response = await fetch("/api/minio", {
+ method: "POST",
+ body: formData,
+ });
+
+ const data = await response.json();
+
+ return { imageName, data };
+ } catch (error) {
+ console.error("画像のアップロードに失敗しました:", error);
+ throw error;
+ }
+ };
+
+ const getCaption = async (
+ imageName: string
+ ): Promise<{ caption: string }> => {
+ try {
+ const response = await fetch(`/api/image?imageName=${imageName}`);
+ if (!response.ok) {
+ throw new Error("キャプションの取得に失敗しました");
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error("キャプションの取得に失敗しました:", error);
+ throw error;
+ }
+ };
+
+ // スコア計算を行います。
+ const similarityRequest = async (caption: string) => {
+ const words: string[] = shapeCaption(caption);
+ const assignmentWord: string = todayAssignment?.english || "";
+ const resSimilarity = await postSimilarity(assignmentWord, words);
+ return {
+ similarity: resSimilarity.similarity as number,
+ assignmentId: todayAssignment?.assignmentId as number,
+ };
+ };
+
+ // userIdの取得
+ const getUserId = async () => {
+ const userString = localStorage.getItem("userID");
+ if (userString === null) {
+ return null;
+ }
+ const userData = JSON.parse(userString);
+
+ const resUserId = await fetch("/api/user?uid=" + userData.uid, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ return await resUserId.json();
+ };
+
+ // scoreの送信
+ const submitScore = async (scoreData: ScoreData) => {
+ const response = await fetch("/api/score", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ scoreData }),
+ });
+
+ if (!response.ok) {
+ console.error("スコアの送信に失敗しました", response.statusText);
+ return;
+ }
+
+ return await response.json();
+ };
+
+ const handleConfirm = async () => {
+ if (tempImage) {
+ try {
+ const { imageName } = await uploadImage(tempImage);
+ const imageURL = `${process.env.NEXT_PUBLIC_MINIO_ENDPOINT}${BUCKET_NAME}/${imageName}`;
+
+ const res = await getCaption(imageName);
+ const caption = res.caption;
+
+ setShowConfirmDialog(false);
+ setImage(tempImage);
+ setShowImage(true);
+ setTempImage(null);
+
+ const { similarity, assignmentId } = await similarityRequest(caption);
+
+ const user = await getUserId();
+ const userId: number = user.id;
+
+ const scoreData: ScoreData = {
+ similarity: similarity,
+ answerTime: new Date(),
+ imageUrl: imageURL,
+ assignmentId: assignmentId,
+ userId: userId,
+ };
+ const response = await submitScore(scoreData);
+ const score = response.score;
+ const percentSimilarity = Math.floor(similarity * 100);
+ const message = `${caption} 類似度 ${percentSimilarity}% スコア: ${score.point} ランキングから順位を確認しましょう!`;
+ const newAssignments = assignments.map((assignment) => {
+ if (assignment.assignmentId === assignmentId) {
+ assignment.isAnswered = true;
+ }
+ return assignment;
+ });
+ const notAnsweredAssignment = newAssignments.find(
+ (assignment: todayAssignment) => !assignment.isAnswered
+ );
+
+ setTodayAssignment(notAnsweredAssignment);
+
+ setIsUploading(false);
+ toast(message);
+ setAssignments(newAssignments);
+
+ if (newAssignments.every((assignment) => assignment.isAnswered)) {
+ setIsActive(false);
+ }
+ } catch (error) {
+ setIsUploading(false);
+ console.error("アップロード中にエラーが発生しました:", error);
+ }
+ }
+ };
+
+ const handleCancel = () => {
+ setShowConfirmDialog(false);
+ setTempImage(null);
+ };
+
+ const handleImageCapture = (capturedImage: string | ImageData) => {
+ const imageStr =
+ capturedImage instanceof ImageData
+ ? imageDataToBase64(capturedImage)
+ : capturedImage;
+
+ setTempImage(imageStr);
+ setShowConfirmDialog(true);
+ };
+
+ return (
+ <>
+ {isActive ? (
+ <>
+
+
+
+
+
+
+
{
+ const file = event.target.files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ handleImageCapture(reader.result as string);
+ };
+ reader.readAsDataURL(file);
+ }
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ 画像のアップロード確認
+
+
+ この画像をアップロードしてもよろしいですか?
+
+
+
+
+
+
+
+ いいえ
+
+
+ はい
+
+
+
+
+ {isUploading && }
+
+ {todayAssignment?.english && (
+
+ )}
+ >
+ ) : (
+
+ )}
+ >
+ );
};
export default CameraApp;