ImageUploader

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit be557ea7210cc68adb3b013ab5299203ce78a172
parent c97af9d73b93b90d150d48b6b2b03fdfedccc5f7
Author: 諒太 林 <ryouturn@gmail.com>
Date:   Tue, 16 Jul 2024 21:33:23 +0900

add uploader

Diffstat:
MREADME.md | 3+++
Asrc/app/fileUploader.ts | 20++++++++++++++++++++
Msrc/app/fileuploadform.tsx | 184+++++--------------------------------------------------------------------------
Msrc/app/page.tsx | 4++--
Msrc/app/server.ts | 14--------------
5 files changed, 36 insertions(+), 189 deletions(-)

diff --git a/README.md b/README.md @@ -40,6 +40,9 @@ flowchart upload formの参考 https://tech.andpad.co.jp/entry/2023/12/10/100000 +fileUploaderの参考 +https://zenn.dev/moozaru/articles/b6d46e5787f052 + ## 開発マニュアル(簡易版) リンクしてあるProjectのTodoを見て、できそうなことがあったら、自分をassignしてください。 作業は自分のところにforkしてもらって、プルリクを投げてください。 diff --git a/src/app/fileUploader.ts b/src/app/fileUploader.ts @@ -0,0 +1,19 @@ +"use server"; +import { promises as fs } from "node:fs"; +import { resolve } from "node:path"; +import { revalidatePath } from "next/cache"; + +export async function uploadFile(formData: FormData) { + const file = formData.get("file") as File; + if (file && file.size > 0) { + const data = await file.arrayBuffer(); + const buffer = Buffer.from(data); + const filePath = resolve( + process.cwd(), + "./public/uploads", + `${crypto.randomUUID()}.${file.name.split(".").pop()}`, + ); + await fs.writeFile(filePath, buffer); + } + revalidatePath("/file"); +}+ \ No newline at end of file diff --git a/src/app/fileuploadform.tsx b/src/app/fileuploadform.tsx @@ -1,173 +1,12 @@ -import type { NextPage } from "next"; -import React, { useState, useCallback } from "react"; -import { useDropzone } from "react-dropzone"; -import type { FileRejection } from "react-dropzone"; - -const FileUploader: NextPage = () => { - const [currentShowFiles, setCurrentShowFiles] = useState< - { file: File; isUploaded: boolean }[] - >([]); - - const onUploadFile = async (file: File) => { - try { - setCurrentShowFiles((prevFiles) => [ - ...prevFiles, - { file, isUploaded: false }, - ]); - - const uploadTime = Math.random() * 9000 + 1000; // 1秒から10秒 - await new Promise((resolve) => setTimeout(resolve, uploadTime)); - - setCurrentShowFiles((prevFiles) => - prevFiles.map((f) => - f.file.name === file.name ? { ...f, isUploaded: true } : f, - ), - ); - } catch (error) { - // ↓ここでエラーに関するユーザーへの通知や処理を行う - alert(`アップロード中にエラーが発生しました: ${error}`); - } - }; - - const onDrop = useCallback( - async (acceptedFiles: File[]) => { - // ドロップしたファイルの中で、現在表示されているファイルと重複しているもの( filename と size が同じファイル)を除外する。 - const filteringFiles = acceptedFiles.filter( - (file) => - !currentShowFiles?.find( - (showFile) => - file.name === showFile.file.name && - file.size === showFile.file.size, - ), - ); - - // ドロップしたファイルと現在表示されているファイルの合計が 10 を超える場合、追加を許可しない。 - if (filteringFiles.length + currentShowFiles.length > 10) { - alert("最大10ファイルまでアップロードできます。"); - return; - } - - // アップロード可能なファイルが存在する場合、アップロード中のスイッチを true にし、アップロードを開始する - if (filteringFiles.length) { - try { - await Promise.all(filteringFiles.map((file) => onUploadFile(file))); - // ↓すべてのファイルのアップロードが成功した後の処理を書く - } catch (error) { - // ↓ここでエラーに関するユーザーへの通知や処理を行う - alert(`アップロード中にエラーが発生しました: ${error}`); - } - } - }, - [currentShowFiles], - ); - - const onDropRejected = useCallback((rejectedFiles: FileRejection[]) => { - rejectedFiles.forEach(({ file, errors }) => { - errors.forEach(({ code }) => { - let message = "エラーが発生しました。"; - switch (code) { - case "file-too-large": - message = `${file.name} のファイルサイズが大きすぎます。50MB以下のファイルをアップロードしてください。`; - break; - case "file-invalid-type": - message = `${file.name} のファイル形式が許可されていません。許可されているファイル形式は jpg, png, pdf, doc, docx, xls, xlsx, ppt, pptx です。`; - break; - default: - break; - } - alert(message); - }); - }); - }, []); - - const { getRootProps, getInputProps, isDragAccept, isDragReject } = - useDropzone({ - onDrop, - onDropRejected, - accept: { - "image/jpeg": [], - "image/png": [], - "application/pdf": [], - }, - maxSize: 50 * 1024 * 1024, // 50MB - }); - - // ドラッグ中のスタイルを設定 - - const removeFile = (index: number) => { - const filteringFiles = currentShowFiles.filter( - (_, i) => i !== index, - ); - setCurrentShowFiles(filteringFiles); - }; - - return ( - <div> - <div> - <div - {...getRootProps()} - > - <input {...getInputProps()} /> - <p> - {isDragAccept - ? "ファイルをアップロードします。" - : isDragReject - ? "エラー" - : "ファイルを登録してください。"} - </p> - <p> - {isDragReject - ? "このファイル形式のアップロードは許可されていません。" - : "ファイルを選択するか、ドラッグアンドドロップしてください。"} - </p> - <button disabled={isDragReject}>ファイルを選択</button> - </div> - <p> - 複数のファイルを選択できます。pdf, png, jpg, jpeg - ファイルを選択できます。 - </p> - <p>※1ファイルの最大サイズは50MBです</p> - </div> - {currentShowFiles && ( - <aside> - <ul> - {currentShowFiles.map((item, index) => ( - <li key={index}> - {item.isUploaded ? ( - <div> - <div> - <span> - </span> - </div> - <div> - <p>{item.file.name}</p> - </div> - <button - type="button" - onClick={() => { - removeFile(index); - }} - > - <span> - </span> - </button> - </div> - ) : ( - <div> - <div> - <p> - {item.file.name}をアップロードしています… - </p> - </div> - </div> - )} - </li> - ))} - </ul> - </aside> - )} - </div> - ); +'use client'; +import { Button, Input } from "@mui/material"; +import { uploadFile } from "./fileUploader"; + +export default function UploadForm() { + return( + <form action={uploadFile}> + <Input type="file" name="file" sx={{marginRight:'10px'}}/> + <Button type="submit" variant="contained" size="small">アップロード</Button> + </form> + ) }; - -export default FileUploader;- \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx @@ -17,7 +17,7 @@ import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar'; import IconButton from '@mui/material/IconButton'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import FileUploader from './fileUploadForm'; +import UploadForm from './fileUploadForm'; import HomeIcon from '@mui/icons-material/Home'; import ImageSearchIcon from '@mui/icons-material/ImageSearch'; import HelpIcon from '@mui/icons-material/Help'; @@ -160,7 +160,7 @@ export default function Home() { <p>Files posted here will be deleted after a set period of time.</p> </div> <Box> - <FileUploader/> + <UploadForm/> </Box> <Box> <h4>URLs</h4> diff --git a/src/app/server.ts b/src/app/server.ts @@ -1,13 +0,0 @@ -'use server' -import fs from "node:fs/promises"; -import { revalidatePath } from "next/cache"; - -export async function uploadFile(formData: FormData) { - const file = formData.get("file") as File; - const arrayBuffer = await file.arrayBuffer(); - const buffer = new Uint8Array(arrayBuffer); - - await fs.writeFile(`./public/uploads/${file.name}`, buffer); - - revalidatePath("/"); -}- \ No newline at end of file