add a basic UI
This commit is contained in:
parent
e8908ac782
commit
2bf4b62082
18
frontend/.eslintrc.cjs
Normal file
18
frontend/.eslintrc.cjs
Normal 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
24
frontend/.gitignore
vendored
Normal 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
17
frontend/index.html
Normal 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
33
frontend/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
27
frontend/src/App.tsx
Normal file
27
frontend/src/App.tsx
Normal 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;
|
||||
119
frontend/src/components/ImageUpload.tsx
Normal file
119
frontend/src/components/ImageUpload.tsx
Normal 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
3
frontend/src/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
frontend/src/main.tsx
Normal file
10
frontend/src/main.tsx
Normal 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
1
frontend/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
frontend/tailwind.config.js
Normal file
13
frontend/tailwind.config.js
Normal 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
25
frontend/tsconfig.json
Normal 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" }]
|
||||
}
|
||||
10
frontend/tsconfig.node.json
Normal file
10
frontend/tsconfig.node.json
Normal 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
7
frontend/vite.config.ts
Normal 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
1938
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user