├── metadata.json ├── .gitignore ├── index.tsx ├── package.json ├── vite.config.ts ├── README.md ├── tsconfig.json ├── index.html └── App.tsx /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Image Tiler", 3 | "description": "An application to slice an uploaded image into smaller, downloadable tiles based on user-specified dimensions.", 4 | "requestFramePermissions": [] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | import App from './App'; 5 | 6 | const rootElement = document.getElementById('root'); 7 | if (!rootElement) { 8 | throw new Error("Could not find root element to mount to"); 9 | } 10 | 11 | const root = ReactDOM.createRoot(rootElement); 12 | root.render( 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "image-tiler", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^19.1.1", 13 | "react-dom": "^19.1.1" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^22.14.0", 17 | "@vitejs/plugin-react": "^5.0.0", 18 | "typescript": "~5.8.2", 19 | "vite": "^6.2.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineConfig, loadEnv } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | 5 | export default defineConfig(({ mode }) => { 6 | const env = loadEnv(mode, '.', ''); 7 | return { 8 | plugins: [react()], 9 | define: { 10 | 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), 11 | 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) 12 | }, 13 | resolve: { 14 | alias: { 15 | '@': path.resolve(__dirname, '.'), 16 | } 17 | } 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | GHBanner 3 |
4 | 5 | # Run and deploy your AI Studio app 6 | 7 | This contains everything you need to run your app locally. 8 | 9 | View your app in AI Studio: https://ai.studio/apps/drive/1ukhuErKQYk1vk6vMANYKOk9Uayf4pu-s 10 | 11 | ## Run Locally 12 | 13 | **Prerequisites:** Node.js 14 | 15 | 16 | 1. Install dependencies: 17 | `npm install` 18 | 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key 19 | 3. Run the app: 20 | `npm run dev` 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "experimentalDecorators": true, 5 | "useDefineForClassFields": false, 6 | "module": "ESNext", 7 | "lib": [ 8 | "ES2022", 9 | "DOM", 10 | "DOM.Iterable" 11 | ], 12 | "skipLibCheck": true, 13 | "types": [ 14 | "node" 15 | ], 16 | "moduleResolution": "bundler", 17 | "isolatedModules": true, 18 | "moduleDetection": "force", 19 | "allowJs": true, 20 | "jsx": "react-jsx", 21 | "paths": { 22 | "@/*": [ 23 | "./*" 24 | ] 25 | }, 26 | "allowImportingTsExtensions": true, 27 | "noEmit": true 28 | } 29 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Tiler Pro 8 | 9 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react'; 3 | 4 | const UploadIcon: React.FC<{ className?: string }> = ({ className }) => ( 5 | 6 | 7 | 8 | ); 9 | 10 | const DownloadIcon: React.FC<{ className?: string }> = ({ className }) => ( 11 | 12 | 13 | 14 | ); 15 | 16 | const ResetIcon: React.FC<{ className?: string }> = ({ className }) => ( 17 | 18 | 19 | 20 | ); 21 | 22 | const ZoomInIcon: React.FC<{ className?: string }> = ({ className }) => ( 23 | 24 | 25 | 26 | ); 27 | 28 | const ZoomOutIcon: React.FC<{ className?: string }> = ({ className }) => ( 29 | 30 | 31 | 32 | ); 33 | 34 | const FitScreenIcon: React.FC<{ className?: string }> = ({ className }) => ( 35 | 36 | 37 | 38 | ); 39 | 40 | const CopyIcon: React.FC<{ className?: string }> = ({ className }) => ( 41 | 42 | 43 | 44 | ); 45 | 46 | interface ModalState { 47 | isOpen: boolean; 48 | col: number; 49 | row: number; 50 | } 51 | 52 | const SingleTileModal: React.FC<{ 53 | modalData: ModalState; 54 | fileNameTemplate: string; 55 | onClose: () => void; 56 | generateTileDataUrl: (col: number, row: number) => string | null; 57 | downloadDataUrl: (dataUrl: string, filename: string) => void; 58 | }> = ({ modalData, fileNameTemplate, onClose, generateTileDataUrl, downloadDataUrl }) => { 59 | if (!modalData.isOpen) return null; 60 | 61 | const { col, row } = modalData; 62 | 63 | const [fileName, setFileName] = useState(`${fileNameTemplate}_${row}_${col}`); 64 | const [saveTileAsBase64, setSaveTileAsBase64] = useState(false); 65 | const [copied, setCopied] = useState(false); 66 | const [base64Output, setBase64Output] = useState(null); 67 | 68 | useEffect(() => { 69 | if (modalData.isOpen) { 70 | setFileName(`${fileNameTemplate}_${row}_${col}`); 71 | setSaveTileAsBase64(false); 72 | setCopied(false); 73 | setBase64Output(null); 74 | } 75 | }, [modalData, fileNameTemplate, row, col]); 76 | 77 | const handleDownload = () => { 78 | const dataUrl = generateTileDataUrl(col, row); 79 | if (!dataUrl) return; 80 | 81 | if (saveTileAsBase64) { 82 | setBase64Output(dataUrl); 83 | } else { 84 | downloadDataUrl(dataUrl, `${fileName}.png`); 85 | onClose(); 86 | } 87 | }; 88 | 89 | const handleCopy = () => { 90 | if (!base64Output) return; 91 | navigator.clipboard.writeText(base64Output).then(() => { 92 | setCopied(true); 93 | setTimeout(() => setCopied(false), 2000); 94 | }); 95 | }; 96 | 97 | const handleBack = () => { 98 | setBase64Output(null); 99 | }; 100 | 101 | return ( 102 |
103 |
e.stopPropagation()}> 104 | {base64Output ? ( 105 |
106 |

Base64 Строка

107 |