diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..d1b5f22 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI, File, UploadFile +from fastapi.middleware.cors import CORSMiddleware + +from typing import Annotated + +import dotenv +import os + +import asyncio +import uvicorn + + +dotenv.load_dotenv() +disable_docs = os.getenv("DISABLE_DOCS", "true").lower() == "true" +app: FastAPI = FastAPI(title="This OR That", + summary="OpenAPI schema for \"This OR That\" project!", + version="0.1", + contact={"GitHub": "https://github.com/IgorVolochay/thisORthat"}, + docs_url=None if disable_docs else "/docs", + redoc_url=None if disable_docs else "/redoc", + openapi_url=None if disable_docs else "/openapi.json") + + +origins = ["*"] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/") +def root(): + return "Hello!" + +@app.post("/upload_file") +@app.post("/upload_file") +async def create_file(file: Annotated[bytes, File()]): + return {"file_size": len(file)} + +async def main(): + config = uvicorn.Config("main:app", port=5000, log_level="debug") + server = uvicorn.Server(config) + await server.serve() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..9f799ca --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "drop-me-files-analog" +version = "0.1.0" +description = "" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "dotenv>=0.9.9", + "fastapi>=0.121.1", + "python-multipart>=0.0.20", + "uvicorn>=0.38.0", +] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..6786f81 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,25 @@ + + + + + + Аналог DropMeFiles + + + +

Аналог DropMeFiles

+
+
+ + +
+
+
+ + + + diff --git a/frontend/script.js b/frontend/script.js new file mode 100644 index 0000000..6f6a61f --- /dev/null +++ b/frontend/script.js @@ -0,0 +1,77 @@ +const fileInput = document.getElementById('fileInput'); +const dropzone = document.getElementById('dropzone'); +const uploadButton = document.getElementById('uploadButton'); +const uploadForm = document.getElementById('uploadForm'); +const statusDiv = document.getElementById('status'); + +const updateButtonState = () => { + uploadButton.disabled = !fileInput.files || fileInput.files.length === 0; +}; + +const setStatus = (message, type = '') => { + statusDiv.textContent = message; + statusDiv.className = type ? `status ${type}` : 'status'; +}; + +dropzone.addEventListener('dragover', (event) => { + event.preventDefault(); + dropzone.classList.add('dragover'); +}); + +dropzone.addEventListener('dragleave', () => { + dropzone.classList.remove('dragover'); +}); + +dropzone.addEventListener('drop', (event) => { + event.preventDefault(); + dropzone.classList.remove('dragover'); + + if (event.dataTransfer?.files?.length) { + fileInput.files = event.dataTransfer.files; + updateButtonState(); + } +}); + +fileInput.addEventListener('change', () => { + updateButtonState(); +}); + +uploadForm.addEventListener('submit', async (event) => { + event.preventDefault(); + + if (!fileInput.files || fileInput.files.length === 0) { + setStatus('Выберите файлов для загрузки.', 'error'); + return; + } + + const formData = new FormData(); + Array.from(fileInput.files).forEach((file) => { + formData.append('files', file); + }); + + uploadButton.disabled = true; + dropzone.classList.remove('dragover'); + setStatus('Загрузка...', ''); + + try { + const response = await fetch('http://192.168.0.132:5000/upload_file', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + throw new Error(`Ошибка сервера: ${response.status}`); + } + + const data = await response.text(); + setStatus(`Путь к файлу: ${data}`, 'success'); + fileInput.value = ''; + } catch (error) { + setStatus(`Не удалось загрузить файлы: ${error.message}`, 'error'); + } finally { + uploadButton.disabled = false; + } +}); + +updateButtonState(); + diff --git a/frontend/style.css b/frontend/style.css new file mode 100644 index 0000000..3036f50 --- /dev/null +++ b/frontend/style.css @@ -0,0 +1,110 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: Arial, sans-serif; + background: radial-gradient(circle at top, #f7f7fb, #eff1f8); + color: #111827; + padding: 24px; +} + +h1 { + font-size: clamp(32px, 4vw, 48px); + margin-bottom: 32px; + text-align: center; +} + +.upload-container { + width: min(640px, 100%); + background: #ffffffdd; + border-radius: 20px; + box-shadow: 0 20px 45px -20px rgba(15, 23, 42, 0.65); + padding: 32px; + display: flex; + flex-direction: column; + gap: 24px; + backdrop-filter: blur(8px); +} + +.dropzone { + border: 2px dashed #6366f1; + border-radius: 16px; + padding: 48px 24px; + text-align: center; + transition: all 0.2s ease; + background: rgba(99, 102, 241, 0.04); + cursor: pointer; + position: relative; +} + +.dropzone.dragover { + background: rgba(99, 102, 241, 0.12); + transform: translateY(-2px); + box-shadow: 0 14px 28px -24px rgba(99, 102, 241, 0.65); +} + +.dropzone input[type="file"] { + position: absolute; + inset: 0; + opacity: 0; + cursor: pointer; +} + +.dropzone p { + margin: 8px 0; + color: #4b5563; + font-size: 16px; +} + +.dropzone p.strong { + font-weight: 600; + color: #1f2937; + font-size: 18px; +} + +button { + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + border: none; + border-radius: 12px; + padding: 14px 20px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 16px 32px -24px rgba(99, 102, 241, 0.8); +} + +button:disabled { + background: #cbd5f5; + cursor: not-allowed; + box-shadow: none; +} + +.status { + min-height: 24px; + font-size: 15px; + color: #374151; + line-height: 1.5; + word-break: break-all; +} + +.status.error { + color: #dc2626; +} + +.status.success { + color: #166534; +} +