add a basic UI

This commit is contained in:
Abi Raja 2023-11-14 13:10:41 -05:00
parent e8908ac782
commit 2bf4b62082
15 changed files with 2251 additions and 0 deletions

18
frontend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

17
frontend/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/svg+xml"
href="https://picoapps.xyz/favicon.png"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Screenshot to Code</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

33
frontend/package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "screenshot-to-code",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"autoprefixer": "^10.4.16",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
}

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

27
frontend/src/App.tsx Normal file
View File

@ -0,0 +1,27 @@
import { useState } from "react";
import ImageUpload from "./components/ImageUpload";
function App() {
const [referenceImages, setReferenceImages] = useState<string[]>([]);
return (
<>
<h1 className="text-2xl">Drag & Drop a Screenshot</h1>
{referenceImages.length > 0 && (
<img
className="w-[300px]"
src={referenceImages[0].data}
alt="Reference"
/>
)}
{referenceImages.length === 0 && (
<>
<ImageUpload setReferenceImages={setReferenceImages} />
</>
)}
</>
);
}
export default App;

View File

@ -0,0 +1,119 @@
import { useState, useEffect, useMemo } from "react";
import { useDropzone } from "react-dropzone";
// import { PromptImage } from "../../../types";
import { toast } from "react-hot-toast";
const baseStyle = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 2,
borderColor: "#eeeeee",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#bdbdbd",
outline: "none",
transition: "border .24s ease-in-out",
};
const focusedStyle = {
borderColor: "#2196f3",
};
const acceptStyle = {
borderColor: "#00e676",
};
const rejectStyle = {
borderColor: "#ff1744",
};
// TODO: Move to a seperate file
function fileToDataURL(file: File) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
}
type FileWithPreview = {
preview: string;
} & File;
interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setReferenceImages: React.Dispatch<React.SetStateAction<any[]>>;
}
function ImageUpload({ setReferenceImages }: Props) {
const [files, setFiles] = useState<FileWithPreview[]>([]);
const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } =
useDropzone({
maxFiles: 1,
maxSize: 1024 * 1024 * 5, // 5 MB
accept: {
"image/png": [".png"],
"image/jpeg": [".jpeg"],
"image/jpg": [".jpg"],
},
onDrop: (acceptedFiles) => {
// Set up the preview thumbnail images
setFiles(
acceptedFiles.map((file: File) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
) as FileWithPreview[]
);
// Convert images to data URLs and set the prompt images state
Promise.all(acceptedFiles.map((file) => fileToDataURL(file)))
.then((dataUrls) => {
setReferenceImages(
dataUrls.map((dataUrl, index) => ({
filename: acceptedFiles[index].name,
data: dataUrl as string,
}))
);
})
.catch((error) => {
// TODO: Display error to user
console.error("Error reading files:", error);
});
},
onDropRejected: (rejectedFiles) => {
toast.error(rejectedFiles[0].errors[0].message);
},
});
useEffect(() => {
return () => files.forEach((file) => URL.revokeObjectURL(file.preview));
}, [files]); // Added files as a dependency
const style = useMemo(
() => ({
...baseStyle,
...(isFocused ? focusedStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {}),
}),
[isFocused, isDragAccept, isDragReject]
);
return (
<section className="container">
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
<div {...getRootProps({ style: style as any })}>
<input {...getInputProps()} />
<p>Drag and drop a screenshot or mock up here, or click to select</p>
</div>
</section>
);
}
export default ImageUpload;

3
frontend/src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

10
frontend/src/main.tsx Normal file
View File

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

1
frontend/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
button: "#ffd803",
highlight: "#ffd803",
},
},
},
plugins: [],
};

25
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

7
frontend/vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

1938
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff