├── .gitignore ├── LICENSE ├── README.md ├── bun.lockb ├── docs ├── .gitignore ├── LICENSE ├── api │ ├── UploadButton.mdx │ ├── UploadDropzone.mdx │ ├── apiExtract.ts │ └── tsconfig.json ├── bun.lockb ├── components │ └── UseUploadFilesExample.tsx ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages │ ├── _app.tsx │ ├── _meta.tsx │ ├── api-reference │ │ ├── UploadButton.mdx │ │ ├── UploadDropzone.mdx │ │ ├── _meta.tsx │ │ ├── uploadFiles.mdx │ │ └── useUploadFiles.mdx │ ├── getting-started.mdx │ ├── getting-started │ │ └── server-setup.mdx │ ├── global.css │ ├── index.tsx │ ├── introduction.mdx │ └── serving-files.mdx ├── postcss.config.js ├── public │ └── favicon.ico ├── tailwind.config.js ├── theme.config.tsx └── tsconfig.json ├── examples └── simple │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── convex │ ├── README.md │ ├── _generated │ │ ├── api.d.ts │ │ ├── api.js │ │ ├── dataModel.d.ts │ │ ├── server.d.ts │ │ └── server.js │ ├── files.ts │ ├── schema.ts │ └── tsconfig.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── main.tsx │ ├── useEvent.ts │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── lib ├── UploadButton.tsx ├── UploadDropzone.tsx ├── UploadSpinner.tsx ├── index.ts ├── react.ts ├── styles.css ├── uploadFiles.ts ├── useEvent.ts └── useUploadFiles.ts ├── package-lock.json ├── package.json ├── styles.css ├── tailwind.config.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | 15 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 16 | 17 | # Runtime data 18 | 19 | pids 20 | _.pid 21 | _.seed 22 | \*.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | 30 | coverage 31 | \*.lcov 32 | 33 | # nyc test coverage 34 | 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 38 | 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | 43 | bower_components 44 | 45 | # node-waf configuration 46 | 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | 51 | build/Release 52 | 53 | # Dependency directories 54 | 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | 60 | web_modules/ 61 | 62 | # TypeScript cache 63 | 64 | *.tsbuildinfo 65 | 66 | # Optional npm cache directory 67 | 68 | .npm 69 | 70 | # Optional eslint cache 71 | 72 | .eslintcache 73 | 74 | # Optional stylelint cache 75 | 76 | .stylelintcache 77 | 78 | # Microbundle cache 79 | 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | 87 | .node_repl_history 88 | 89 | # Output of 'npm pack' 90 | 91 | \*.tgz 92 | 93 | # Yarn Integrity file 94 | 95 | .yarn-integrity 96 | 97 | # dotenv environment variable files 98 | 99 | .env 100 | .env.development.local 101 | .env.test.local 102 | .env.production.local 103 | .env.local 104 | 105 | # parcel-bundler cache (https://parceljs.org/) 106 | 107 | .cache 108 | .parcel-cache 109 | 110 | # Next.js build output 111 | 112 | .next 113 | out 114 | 115 | # Nuxt.js build / generate output 116 | 117 | .nuxt 118 | dist 119 | 120 | # Gatsby files 121 | 122 | .cache/ 123 | 124 | # Comment in the public line in if your project uses Gatsby and not Next.js 125 | 126 | # https://nextjs.org/blog/next-9-1#public-directory-support 127 | 128 | # public 129 | 130 | # vuepress build output 131 | 132 | .vuepress/dist 133 | 134 | # vuepress v2.x temp and cache directory 135 | 136 | .temp 137 | .cache 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.\* 170 | .vercel 171 | 172 | 173 | esm/ 174 | cjs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 xixixao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Parts of code adapted from https://github.com/pingdotgg/uploadthing. 24 | 25 | MIT License 26 | 27 | Copyright (c) 2023 Ping Labs 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UploadStuff 2 | 3 | ## Install 4 | 5 | `npm install @xixixao/uploadstuff` 6 | 7 | ## Docs 8 | 9 | [https://uploadstuff.dev](https://uploadstuff.dev) 10 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/get-convex/uploadstuff/ccefb233e133f6bd6f07cb7c26dc01cb132f12f0/bun.lockb -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | .vercel 4 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shu Ding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/api/UploadButton.mdx: -------------------------------------------------------------------------------- 1 | | Prop | Required | Type | Description | 2 | | --- | --- | --- | --- | 3 | | uploadUrl | Yes | string | (() => Promise<string>) | Either the absolute upload URL or an async function that generates it | 4 | | fileTypes | No | string[] | A list of [file type specifiers]((https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers)) | 5 | | multiple | No | boolean | Whether the user can select multiple files to upload. Defaults to `false` | 6 | | onUploadProgress | No | (progress: number) => void | Called every time the combined upload progresses by at least 10 percent. `progress` % is a multiple of 10. | 7 | | onUploadBegin | No | (fileName: string) => void | Called at the start of each upload. | 8 | | onUploadComplete | No | (uploaded: UploadFileResponse[]) => Promise<void> | void | Called when all the files have been uploaded. | 9 | | onUploadError | No | (error: unknown) => void | Called if there was an error at any point in the upload process. | 10 | | content | No | (progress: number | null) => string | Replaces the content shown on the button. `progress` % is a multiple of 10 if the upload is in progress or `null`. | 11 | | className | No | (progress: number | null) => string | Replaces the `className` of the button. `progress` % is a multiple of 10 if the upload is in progress or `null`. | -------------------------------------------------------------------------------- /docs/api/UploadDropzone.mdx: -------------------------------------------------------------------------------- 1 | | Prop | Required | Type | Description | 2 | | --- | --- | --- | --- | 3 | | uploadUrl | Yes | string | (() => Promise<string>) | Either the absolute upload URL or an async function that generates it | 4 | | fileTypes | No | Accept | An object of with a common [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) as keys and an array of file extensions as values (similar to [showOpenFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker)'s types accept option) | 5 | | multiple | No | boolean | Whether the user can select multiple files to upload. Defaults to `false` | 6 | | uploadImmediately | No | boolean | Whether the upload should start right after the user drags the file in. Defaults to `false` | 7 | | onUploadProgress | No | (progress: number) => void | Called every time the combined upload progresses by at least 10 percent. `progress` % is a multiple of 10. | 8 | | onUploadBegin | No | (fileName: string) => void | Called at the start of each upload. | 9 | | onUploadComplete | No | (uploaded: UploadFileResponse[]) => Promise<void> | void | Called when all the files have been uploaded. | 10 | | onUploadError | No | (error: unknown) => void | Called if there was an error at any point in the upload process. | 11 | | subtitle | No | string | Text, if provided, is shown below the "Choose files" line | 12 | | content | No | (state: UploadDropzoneState) => string | Replaces all of the content shown in the dropzone. `progress` % is a multiple of 10 if the upload is in progress or `null`. | 13 | | className | No | (state: UploadDropzoneState) => string | Replaces the `className` of the dropzone. `progress` % is a multiple of 10 if the upload is in progress or `null`. | -------------------------------------------------------------------------------- /docs/api/apiExtract.ts: -------------------------------------------------------------------------------- 1 | await extractComponent("UploadButton"); 2 | await extractComponent("UploadDropzone"); 3 | 4 | async function extractComponent(name: string) { 5 | const file = Bun.file(`../lib/${name}.tsx`); 6 | 7 | const text = await file.text(); 8 | 9 | const typeSignaturePattern = `${name}${/\(\w+: {\n([\s\S]+?)\n}/.source}`; 10 | 11 | const typeSignature = text.match(typeSignaturePattern)?.[1]; 12 | 13 | const pattern = 14 | /(?:(?:^|\n)\s+\/\/ (?[^\n]+))?(?:^|\n)\s+(?\w+)(?\?)?: (?[^;]+);/g; 15 | 16 | const types = Array.from(typeSignature?.matchAll(pattern) ?? []); 17 | 18 | const printed = 19 | "| Prop | Required | Type | Description |\n" + 20 | "| --- | --- | --- | --- |\n" + 21 | types 22 | .map( 23 | ({ groups }) => 24 | `| ${groups!.name} | ${ 25 | groups!.optional ? "No" : "Yes" 26 | } | ${groups!.type 27 | .replace(/\|/g, "|") 28 | .replace(/ 11 |
12 | { 16 | const files = Array.from(e.target.files); 17 | if (files.length === 0) { 18 | return; 19 | } 20 | await startUpload(files); 21 | setUploaded( 22 | "✓ Uploaded " + files.map((file) => file.name).join(", ") 23 | ); 24 | }} 25 | /> 26 |
27 | {uploaded} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /docs/next.config.mjs: -------------------------------------------------------------------------------- 1 | import nextra from "nextra"; 2 | 3 | import path from "path"; 4 | 5 | const withNextra = nextra({ 6 | theme: "nextra-theme-docs", 7 | themeConfig: "./theme.config.tsx", 8 | defaultShowCopyCode: true, 9 | }); 10 | 11 | export default withNextra({ 12 | experimental: { 13 | externalDir: true, 14 | }, 15 | webpack: ( 16 | config, 17 | { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }, 18 | ) => { 19 | // Important: return the modified config 20 | return { 21 | ...config, 22 | resolve: { 23 | ...config.resolve, 24 | fallback: { 25 | "react/jsx-runtime": "react/jsx-runtime.js", 26 | "react/jsx-dev-runtime": path.resolve( 27 | import.meta.dirname, 28 | "node_modules/react/jsx-dev-runtime.js", 29 | ), 30 | react: path.resolve(import.meta.dirname, "node_modules/react"), 31 | "react-dropzone": path.resolve( 32 | import.meta.dirname, 33 | "node_modules/react-dropzone", 34 | ), 35 | }, 36 | }, 37 | }; 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextra-docs-template", 3 | "version": "0.0.1", 4 | "description": "Nextra docs template", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/shuding/nextra-docs-template.git" 13 | }, 14 | "author": "Shu Ding ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/shuding/nextra-docs-template/issues" 18 | }, 19 | "homepage": "https://github.com/shuding/nextra-docs-template#readme", 20 | "dependencies": { 21 | "next": "^15.0.2", 22 | "nextra": "^3.2.0", 23 | "nextra-theme-docs": "^3.2.0", 24 | "react": "file:../node_modules/react", 25 | "react-dom": "file:../node_modules/react-dom", 26 | "react-dropzone": "file:../node_modules/react-dropzone", 27 | "uploadstuff": "file:.." 28 | }, 29 | "devDependencies": { 30 | "@types/node": "18.11.10", 31 | "autoprefixer": "^10.4.15", 32 | "bun-types": "^1.0.1", 33 | "postcss": "^8.4.29", 34 | "tailwindcss": "^3.3.3", 35 | "typescript": "file:../node_modules/typescript" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "./global.css"; 2 | import "../../lib/styles.css"; 3 | 4 | export default function App({ Component, pageProps }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /docs/pages/_meta.tsx: -------------------------------------------------------------------------------- 1 | export default { 2 | introduction: "Introduction", 3 | "getting-started": "Getting Started", 4 | "api-reference": "API Reference", 5 | contact: { 6 | title: "Community ↗", 7 | type: "page", 8 | href: "https://www.convex.dev/community", 9 | newWindow: true, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /docs/pages/api-reference/UploadButton.mdx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "nextra/components"; 2 | import { UploadButton } from "uploadstuff/react"; 3 | import Props from "../../api/UploadButton.mdx"; 4 | 5 | # UploadButton 6 | 7 | A simple button that opens the native file picker and uploads the selected 8 | files. 9 | 10 | ## Live demo 11 | 12 | The button is shown below, **try it out**! 13 | 14 |
15 | 16 | 17 | 18 | ## Example source code 19 | 20 | 21 | 22 | 23 | 24 | ```tsx filename="src/App.tsx" 25 | import { useMutation } from "convex/react"; 26 | import { UploadButton, UploadFileResponse } from "@xixixao/uploadstuff/react"; 27 | import "@xixixao/uploadstuff/react/styles.css"; 28 | import { api } from "../convex/_generated/api"; 29 | 30 | export function App() { 31 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 32 | const saveStorageId = useMutation(api.files.saveStorageId); 33 | const saveAfterUpload = async (uploaded: UploadFileResponse[]) => { 34 | await saveStorageId({ storageId: (uploaded[0].response as any).storageId }); 35 | }; 36 | 37 | return ( 38 | { 43 | // Do something with the error. 44 | alert(`ERROR! ${error}`); 45 | }} 46 | /> 47 | ); 48 | } 49 | ``` 50 | 51 | 52 | 53 | 54 | 55 | ```tsx filename="src/App.tsx" 56 | import { useMutation } from "convex/react"; 57 | import { UploadButton, UploadFileResponse } from "@xixixao/uploadstuff/react"; 58 | import "@xixixao/uploadstuff/react/styles.css"; 59 | import { api } from "../convex/_generated/api"; 60 | 61 | export function App() { 62 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 63 | const saveStorageIds = useMutation(api.files.saveStorageIds); 64 | const saveAfterUpload = async (uploaded: UploadFileResponse[]) => { 65 | await saveStorageIds({ 66 | storageIds: uploaded.map(({ response }) => ({ 67 | storageId: (response as any).storageId, 68 | })), 69 | }); 70 | }; 71 | 72 | return ( 73 | { 79 | // Do something with the error. 80 | alert(`ERROR! ${error}`); 81 | }} 82 | /> 83 | ); 84 | } 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | ## Props 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/pages/api-reference/UploadDropzone.mdx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "nextra/components"; 2 | import { UploadDropzone } from "uploadstuff/react"; 3 | import Props from "../../api/UploadDropzone.mdx"; 4 | 5 | # UploadDropzone 6 | 7 | A simple button that opens the native file picker and uploads the selected 8 | files. 9 | 10 | ## Live demo 11 | 12 | The button is shown below, **try it out**! 13 | 14 |
15 | 16 | 20 | 21 | ## Example source code 22 | 23 | 24 | 25 | 26 | 27 | ```tsx filename="src/App.tsx" 28 | import { useMutation } from "convex/react"; 29 | import { UploadDropzone, UploadFileResponse } from "@xixixao/uploadstuff/react"; 30 | import "@xixixao/uploadstuff/react/styles.css"; 31 | import { api } from "../convex/_generated/api"; 32 | 33 | export function App() { 34 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 35 | const saveStorageId = useMutation(api.files.saveStorageId); 36 | const saveAfterUpload = async (uploaded: UploadFileResponse[]) => { 37 | await saveStorageId({ storageId: (uploaded[0].response as any).storageId }); 38 | }; 39 | 40 | return ( 41 | { 49 | // Do something with the error. 50 | alert(`ERROR! ${error}`); 51 | }} 52 | /> 53 | ); 54 | } 55 | ``` 56 | 57 | 58 | 59 | 60 | 61 | ```tsx filename="src/App.tsx" 62 | import { useMutation } from "convex/react"; 63 | import { UploadDropzone, UploadFileResponse } from "@xixixao/uploadstuff/react"; 64 | import "@xixixao/uploadstuff/react/styles.css"; 65 | import { api } from "../convex/_generated/api"; 66 | 67 | export function App() { 68 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 69 | const saveStorageIds = useMutation(api.files.saveStorageIds); 70 | const saveAfterUpload = async (uploaded: UploadFileResponse[]) => { 71 | await saveStorageIds({ 72 | storageIds: uploaded.map(({ response }) => ({ 73 | storageId: (response as any).storageId, 74 | })), 75 | }); 76 | }; 77 | 78 | return ( 79 | { 88 | // Do something with the error. 89 | alert(`ERROR! ${error}`); 90 | }} 91 | /> 92 | ); 93 | } 94 | ``` 95 | 96 | 97 | 98 | 99 | 100 | ## Props 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/pages/api-reference/_meta.tsx: -------------------------------------------------------------------------------- 1 | export default { 2 | UploadButton: "UploadButton", 3 | UploadDropzone: "UploadDropzone", 4 | useUploadFiles: "useUploadFiles", 5 | uploadFiles: "uploadFiles", 6 | }; 7 | -------------------------------------------------------------------------------- /docs/pages/api-reference/uploadFiles.mdx: -------------------------------------------------------------------------------- 1 | # uploadFiles 2 | 3 | A small helper for implementing file uploads which reports progress for each uploaded file. 4 | 5 | ## Example source code 6 | 7 | ```tsx filename="main.tsx" 8 | import { uploadFiles } from "@xixixao/uploadstuff"; 9 | 10 | const uploaded = await uploadFiles({ 11 | // A list of File objects 12 | files, 13 | // The upload URL string 14 | url, 15 | onUploadProgress: ({ file, progress }) => { 16 | // file is the file name string 17 | // progress is a multiple of 10 between 10 and 100 18 | }, 19 | onUploadBegin: ({ file }) => { 20 | // ... 21 | }, 22 | }); 23 | // The result is list of objects with 24 | // name: the file name string 25 | // type: the file type string 26 | // size: the file size in bytes 27 | // response: whatever your upload API returns for each file 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/pages/api-reference/useUploadFiles.mdx: -------------------------------------------------------------------------------- 1 | import { UseUploadFilesExample } from "../../components/UseUploadFilesExample.tsx"; 2 | 3 | # useUploadFiles 4 | 5 | A [React](https://react.dev/) hook for implementing file upload using completely 6 | custom UI. 7 | 8 | ## Live demo 9 | 10 | An example use case is shown below, **try it out**! 11 | 12 |
13 | 14 | 15 | 16 | ## Example source code 17 | 18 | ```tsx filename="src/App.tsx" 19 | import { useMutation } from "convex/react"; 20 | import { useUploadFiles } from "@xixixao/uploadstuff/react"; 21 | import { api } from "../convex/_generated/api"; 22 | 23 | export function App() { 24 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 25 | const { startUpload } = useUploadFiles(generateUploadUrl); 26 | return ( 27 | { 31 | const files = Array.from(event.target.files); 32 | if (files.length === 0) { 33 | return; 34 | } 35 | // optionally: do something with `files`... 36 | const uploaded = await startUpload(files); 37 | // optionally: do something with the response... 38 | }} 39 | /> 40 | ); 41 | } 42 | ``` 43 | 44 | ## API 45 | 46 | Check out the source code. 47 | -------------------------------------------------------------------------------- /docs/pages/getting-started.mdx: -------------------------------------------------------------------------------- 1 | import { Steps } from "nextra/components"; 2 | 3 | # Getting Started 4 | 5 | ## Package setup 6 | 7 | 8 | 9 | ### Setup a React app (with Convex) 10 | 11 | Follow either the [React (Vite)](https://docs.convex.dev/quickstart/react) or [Next.js](https://docs.convex.dev/quickstart/nextjs) quickstart to get Convex set up in your React app. 12 | 13 | ### Install the library 14 | 15 | ```sh 16 | npm i @xixixao/uploadstuff 17 | ``` 18 | 19 | ### Setup your storage API 20 | 21 | Continue with the [Server Setup](/getting-started/server-setup) guide. 22 | 23 | ### Build your UI 24 | 25 | Choose from one of the provided ways to build your UI: 26 | 27 | - [UploadButton](/api-reference/UploadButton) 28 | - [UploadDropzone](/api-reference/UploadDropzone) 29 | - [useUploadFiles](/api-reference/useUploadFiles) 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/pages/getting-started/server-setup.mdx: -------------------------------------------------------------------------------- 1 | import { Steps, Tabs } from "nextra/components"; 2 | 3 | # Getting started on the server 4 | 5 | We'll be using [Convex](https://convex.dev) to implement our file storage, but 6 | you could use any means of generating an upload URL, as long as you can 7 | authorize it. 8 | 9 | ## Setting up Convex functions for file upload 10 | 11 | We'll use 2 [mutations](https://docs.convex.dev/functions/mutation-functions): 12 | The first one generates an upload URL, the second one saves the file storage ID 13 | in our database. 14 | 15 | 16 | 17 | ### Create a mutation that generates an upload URL 18 | 19 | This mutation basically defines an endpoint that gives our UI a URL to upload 20 | files to. It can also: 21 | 22 | - Verify that the user is authorized to upload files 23 | 24 | ```ts filename="convex/files.ts" 25 | import { mutation } from "./_generated/server"; 26 | 27 | export const generateUploadUrl = mutation({ 28 | args: { 29 | // ... 30 | }, 31 | handler: async (ctx, args) => { 32 | // use `args` and/or `ctx.auth` to authorize the user 33 | // ... 34 | 35 | // Return an upload URL 36 | return await ctx.storage.generateUploadUrl(); 37 | }, 38 | }); 39 | ``` 40 | 41 | ### Create a mutation that saves the file storage ID 42 | 43 | The second endpoint will be called after the file upload is complete, to save 44 | the uploaded files' storage IDs and any metadata in our database. It can also: 45 | 46 | - Reverify that the user is still authorized to upload the file 47 | - Limit the number of uploaded files 48 | - Limit the file size 49 | 50 | 51 | 52 | 53 | 54 | ```ts filename="convex/files.ts" 55 | import { v } from "convex/values"; 56 | import { mutation } from "./_generated/server"; 57 | 58 | export const saveStorageId = mutation({ 59 | // You can customize these as you like 60 | args: { 61 | storageId: v.id("_storage"), 62 | // other args... 63 | }, 64 | handler: async (ctx, args) => { 65 | // use `args` and/or `ctx.auth` to authorize the user 66 | // ... 67 | 68 | // Save the storageId to the database using `insert` 69 | await ctx.db.insert("someTable", { 70 | storageId: args.storageId, 71 | // ... 72 | }); 73 | // or `patch`/`replace` 74 | await ctx.db.patch(someId, { 75 | storageId: args.storageId, 76 | // ... 77 | }); 78 | }, 79 | }); 80 | ``` 81 | 82 | 83 | 84 | 85 | 86 | ```ts filename="convex/files.ts" 87 | import { v } from "convex/values"; 88 | import { mutation } from "./_generated/server"; 89 | 90 | export const saveStorageIds = mutation({ 91 | // You can customize these as you like 92 | args: { 93 | storageIds: v.array( 94 | v.object({ 95 | storageId: v.string(), 96 | }), 97 | ), 98 | // other args... 99 | }, 100 | handler: async (ctx, args) => { 101 | // use `args` and/or `ctx.auth` to authorize the user 102 | // ... 103 | 104 | // Save the storageId to the database using `insert` 105 | await ctx.db.insert("someTable", { 106 | storageIds: args.storageIds.map(({ storageId }) => storageId), 107 | // ... 108 | }); 109 | // or `patch`/`replace` 110 | await ctx.db.patch(someId, { 111 | storageIds: args.storageIds.map(({ storageId }) => storageId), 112 | // ... 113 | }); 114 | }, 115 | }); 116 | ``` 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | In your project you can give these mutations more specific names based on your 125 | use case, and you can create multiple pairs of mutations for different file 126 | uploading use cases you might have in your app. 127 | 128 | With these mutations in place, you're ready to use one of the provided APIs to 129 | implement file uploading in your app's UI. 130 | 131 | [Build your UI](/getting-started#build-your-ui) 132 | -------------------------------------------------------------------------------- /docs/pages/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /docs/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Home() { 4 | return ( 5 |
10 |
14 |
uploadstuff
15 |
16 |
17 |

18 | File Uploads For All Developers 19 |

20 |
21 | Building an AI app? Don't waste your time on implementing file 22 | upload. 23 |
24 |
25 | 33 | Read docs 34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /docs/pages/introduction.mdx: -------------------------------------------------------------------------------- 1 | # What is UploadStuff? 2 | 3 | UploadStuff is the easiest way to add file uploads to your full stack TypeScript application. Many services have tried to build a "better S3", but in our opinion, none found the right compromise of ownership, flexibility and safety. 4 | 5 | Doing this well involves getting three key pieces correct: **File Hosting**, **Server-side Authorization**, and **Client Experience**. 6 | 7 | ## 1. Use managed file hosting 8 | 9 | UploadStuff is agnostic of the hosting provider, but we strongly recommend [Convex](https://convex.dev). 10 | 11 | ## 2. Authorize users on YOUR server 12 | 13 | You can't let just anyone upload files to a storage provider which you pay for. You should authorize uploads on the server. [Convex](convex.dev) makes this really easy. 14 | 15 | ## 3. Give users a great experience 16 | 17 | UploadStuff is an open-source JavaScript and React library for uploading files from your frontend with convenient components, hooks and more. There's an `` and `` for super easy implementation. With `useUploadFiles` you can implement your own custom component. And `uploadFiles` can be used from any JS application. 18 | 19 | ## Wait, I've seen this before! 20 | 21 | UploadStuff is a play on and a fork of [UploadThing](https://uploadthing.com/). 22 | 23 | _Disclaimer: This project is not owned or operated by Convex, but I do work there._ 24 | -------------------------------------------------------------------------------- /docs/pages/serving-files.mdx: -------------------------------------------------------------------------------- 1 | # Serving your uploaded files 2 | 3 | Serving depends on your file storage provider. Check out the Convex [Serving Files docs](https://docs.convex.dev/file-storage/serve-files). 4 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/get-convex/uploadstuff/ccefb233e133f6bd6f07cb7c26dc01cb132f12f0/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx,mdx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /docs/theme.config.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DocsThemeConfig } from "nextra-theme-docs"; 3 | 4 | const config: DocsThemeConfig = { 5 | logo: ( 6 | uploadstuff 7 | ), 8 | project: { 9 | link: "https://github.com/xixixao/uploadstuff", 10 | }, 11 | chat: { 12 | link: "https://www.convex.dev/community", 13 | }, 14 | docsRepositoryBase: "https://github.com/xixixao/uploadstuff/tree/main/docs", 15 | gitTimestamp() { 16 | return <>; 17 | }, 18 | footer: { 19 | content: "UploadStuff © 2023 xixixao. All rights reserved.", 20 | }, 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "incremental": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { 18 | "uploadstuff/react": ["../lib/react.ts"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules", "api"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/simple/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple/.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 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | project: ['./tsconfig.json', './tsconfig.node.json'], 21 | tsconfigRootDir: __dirname, 22 | }, 23 | ``` 24 | 25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 28 | -------------------------------------------------------------------------------- /examples/simple/convex/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Convex functions directory! 2 | 3 | Write your Convex functions here. See 4 | https://docs.convex.dev/using/writing-convex-functions for more. 5 | 6 | A query function that takes two arguments looks like: 7 | 8 | ```ts 9 | // functions.js 10 | import { query } from "./_generated/server"; 11 | import { v } from "convex/values"; 12 | 13 | export const myQueryFunction = query({ 14 | // Validators for arguments. 15 | args: { 16 | first: v.number(), 17 | second: v.string(), 18 | }, 19 | 20 | // Function implementation. 21 | hander: async (ctx, args) => { 22 | // Read the database as many times as you need here. 23 | // See https://docs.convex.dev/database/reading-data. 24 | const documents = await ctx.db.query("tablename").collect(); 25 | 26 | // Arguments passed from the client are properties of the args object. 27 | console.log(args.first, args.second); 28 | 29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data, 30 | // remove non-public properties, or create new objects. 31 | return documents; 32 | }, 33 | }); 34 | ``` 35 | 36 | Using this query function in a React component looks like: 37 | 38 | ```ts 39 | const data = useQuery(api.functions.myQueryFunction, { 40 | first: 10, 41 | second: "hello", 42 | }); 43 | ``` 44 | 45 | A mutation function looks like: 46 | 47 | ```ts 48 | // functions.js 49 | import { mutation } from "./_generated/server"; 50 | import { v } from "convex/values"; 51 | 52 | export const myMutationFunction = mutation({ 53 | // Validators for arguments. 54 | args: { 55 | first: v.string(), 56 | second: v.string(), 57 | }, 58 | 59 | // Function implementation. 60 | hander: async (ctx, args) => { 61 | // Insert or modify documents in the database here. 62 | // Mutations can also read from the database like queries. 63 | // See https://docs.convex.dev/database/writing-data. 64 | const message = { body: args.first, author: args.second }; 65 | const id = await ctx.db.insert("messages", message); 66 | 67 | // Optionally, return a value from your mutation. 68 | return await ctx.db.get(id); 69 | }, 70 | }); 71 | ``` 72 | 73 | Using this mutation function in a React component looks like: 74 | 75 | ```ts 76 | const mutation = useMutation(api.functions.myMutationFunction); 77 | function handleButtonPress() { 78 | // fire and forget, the most common way to use mutations 79 | mutation({ first: "Hello!", second: "me" }); 80 | // OR 81 | // use the result once the mutation has completed 82 | mutation({ first: "Hello!", second: "me" }).then((result) => 83 | console.log(result) 84 | ); 85 | } 86 | ``` 87 | 88 | Use the Convex CLI to push your functions to a deployment. See everything 89 | the Convex CLI can do by running `npx convex -h` in your project root 90 | directory. To learn more, launch the docs with `npx convex docs`. 91 | -------------------------------------------------------------------------------- /examples/simple/convex/_generated/api.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { 13 | ApiFromModules, 14 | FilterApi, 15 | FunctionReference, 16 | } from "convex/server"; 17 | import type * as files from "../files"; 18 | 19 | /** 20 | * A utility for referencing Convex functions in your app's API. 21 | * 22 | * Usage: 23 | * ```js 24 | * const myFunctionReference = api.myModule.myFunction; 25 | * ``` 26 | */ 27 | declare const fullApi: ApiFromModules<{ 28 | files: typeof files; 29 | }>; 30 | export declare const api: FilterApi< 31 | typeof fullApi, 32 | FunctionReference 33 | >; 34 | export declare const internal: FilterApi< 35 | typeof fullApi, 36 | FunctionReference 37 | >; 38 | -------------------------------------------------------------------------------- /examples/simple/convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { anyApi } from "convex/server"; 13 | 14 | /** 15 | * A utility for referencing Convex functions in your app's API. 16 | * 17 | * Usage: 18 | * ```js 19 | * const myFunctionReference = api.myModule.myFunction; 20 | * ``` 21 | */ 22 | export const api = anyApi; 23 | export const internal = anyApi; 24 | -------------------------------------------------------------------------------- /examples/simple/convex/_generated/dataModel.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated data model types. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { DataModelFromSchemaDefinition } from "convex/server"; 13 | import type { DocumentByName, TableNamesInDataModel } from "convex/server"; 14 | import type { GenericId } from "convex/values"; 15 | import schema from "../schema"; 16 | 17 | /** 18 | * The names of all of your Convex tables. 19 | */ 20 | export type TableNames = TableNamesInDataModel; 21 | 22 | /** 23 | * The type of a document stored in Convex. 24 | * 25 | * @typeParam TableName - A string literal type of the table name (like "users"). 26 | */ 27 | export type Doc = DocumentByName< 28 | DataModel, 29 | TableName 30 | >; 31 | 32 | /** 33 | * An identifier for a document in Convex. 34 | * 35 | * Convex documents are uniquely identified by their `Id`, which is accessible 36 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 37 | * 38 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 39 | * 40 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 41 | * strings when type checking. 42 | * 43 | * @typeParam TableName - A string literal type of the table name (like "users"). 44 | */ 45 | export type Id = GenericId; 46 | 47 | /** 48 | * A type describing your Convex data model. 49 | * 50 | * This type includes information about what tables you have, the type of 51 | * documents stored in those tables, and the indexes defined on them. 52 | * 53 | * This type is used to parameterize methods like `queryGeneric` and 54 | * `mutationGeneric` to make them type-safe. 55 | */ 56 | export type DataModel = DataModelFromSchemaDefinition; 57 | -------------------------------------------------------------------------------- /examples/simple/convex/_generated/server.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | ActionBuilder, 14 | HttpActionBuilder, 15 | MutationBuilder, 16 | QueryBuilder, 17 | GenericActionCtx, 18 | GenericMutationCtx, 19 | GenericQueryCtx, 20 | GenericDatabaseReader, 21 | GenericDatabaseWriter, 22 | } from "convex/server"; 23 | import type { DataModel } from "./dataModel.js"; 24 | 25 | /** 26 | * Define a query in this Convex app's public API. 27 | * 28 | * This function will be allowed to read your Convex database and will be accessible from the client. 29 | * 30 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 31 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 32 | */ 33 | export declare const query: QueryBuilder; 34 | 35 | /** 36 | * Define a query that is only accessible from other Convex functions (but not from the client). 37 | * 38 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 39 | * 40 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 41 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 42 | */ 43 | export declare const internalQuery: QueryBuilder; 44 | 45 | /** 46 | * Define a mutation in this Convex app's public API. 47 | * 48 | * This function will be allowed to modify your Convex database and will be accessible from the client. 49 | * 50 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 51 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 52 | */ 53 | export declare const mutation: MutationBuilder; 54 | 55 | /** 56 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 57 | * 58 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 59 | * 60 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 61 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 62 | */ 63 | export declare const internalMutation: MutationBuilder; 64 | 65 | /** 66 | * Define an action in this Convex app's public API. 67 | * 68 | * An action is a function which can execute any JavaScript code, including non-deterministic 69 | * code and code with side-effects, like calling third-party services. 70 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 71 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 72 | * 73 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 74 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 75 | */ 76 | export declare const action: ActionBuilder; 77 | 78 | /** 79 | * Define an action that is only accessible from other Convex functions (but not from the client). 80 | * 81 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 82 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 83 | */ 84 | export declare const internalAction: ActionBuilder; 85 | 86 | /** 87 | * Define an HTTP action. 88 | * 89 | * This function will be used to respond to HTTP requests received by a Convex 90 | * deployment if the requests matches the path and method where this action 91 | * is routed. Be sure to route your action in `convex/http.js`. 92 | * 93 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. 95 | */ 96 | export declare const httpAction: HttpActionBuilder; 97 | 98 | /** 99 | * A set of services for use within Convex query functions. 100 | * 101 | * The query context is passed as the first argument to any Convex query 102 | * function run on the server. 103 | * 104 | * This differs from the {@link MutationCtx} because all of the services are 105 | * read-only. 106 | */ 107 | export type QueryCtx = GenericQueryCtx; 108 | 109 | /** 110 | * A set of services for use within Convex mutation functions. 111 | * 112 | * The mutation context is passed as the first argument to any Convex mutation 113 | * function run on the server. 114 | */ 115 | export type MutationCtx = GenericMutationCtx; 116 | 117 | /** 118 | * A set of services for use within Convex action functions. 119 | * 120 | * The action context is passed as the first argument to any Convex action 121 | * function run on the server. 122 | */ 123 | export type ActionCtx = GenericActionCtx; 124 | 125 | /** 126 | * An interface to read from the database within Convex query functions. 127 | * 128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single 129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts 130 | * building a query. 131 | */ 132 | export type DatabaseReader = GenericDatabaseReader; 133 | 134 | /** 135 | * An interface to read from and write to the database within Convex mutation 136 | * functions. 137 | * 138 | * Convex guarantees that all writes within a single mutation are 139 | * executed atomically, so you never have to worry about partial writes leaving 140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) 141 | * for the guarantees Convex provides your functions. 142 | */ 143 | export type DatabaseWriter = GenericDatabaseWriter; 144 | -------------------------------------------------------------------------------- /examples/simple/convex/_generated/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.2.1. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | actionGeneric, 14 | httpActionGeneric, 15 | queryGeneric, 16 | mutationGeneric, 17 | internalActionGeneric, 18 | internalMutationGeneric, 19 | internalQueryGeneric, 20 | } from "convex/server"; 21 | 22 | /** 23 | * Define a query in this Convex app's public API. 24 | * 25 | * This function will be allowed to read your Convex database and will be accessible from the client. 26 | * 27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 29 | */ 30 | export const query = queryGeneric; 31 | 32 | /** 33 | * Define a query that is only accessible from other Convex functions (but not from the client). 34 | * 35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 36 | * 37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 39 | */ 40 | export const internalQuery = internalQueryGeneric; 41 | 42 | /** 43 | * Define a mutation in this Convex app's public API. 44 | * 45 | * This function will be allowed to modify your Convex database and will be accessible from the client. 46 | * 47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 49 | */ 50 | export const mutation = mutationGeneric; 51 | 52 | /** 53 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 54 | * 55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 56 | * 57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 59 | */ 60 | export const internalMutation = internalMutationGeneric; 61 | 62 | /** 63 | * Define an action in this Convex app's public API. 64 | * 65 | * An action is a function which can execute any JavaScript code, including non-deterministic 66 | * code and code with side-effects, like calling third-party services. 67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 69 | * 70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 72 | */ 73 | export const action = actionGeneric; 74 | 75 | /** 76 | * Define an action that is only accessible from other Convex functions (but not from the client). 77 | * 78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 80 | */ 81 | export const internalAction = internalActionGeneric; 82 | 83 | /** 84 | * Define a Convex HTTP action. 85 | * 86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object 87 | * as its second. 88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. 89 | */ 90 | export const httpAction = httpActionGeneric; 91 | -------------------------------------------------------------------------------- /examples/simple/convex/files.ts: -------------------------------------------------------------------------------- 1 | import { v } from "convex/values"; 2 | import { mutation, query } from "./_generated/server"; 3 | 4 | export const getUrl = query({ 5 | args: { name: v.string() }, 6 | handler: async (ctx, { name }) => { 7 | const file = await ctx.db 8 | .query("files") 9 | .filter((q) => q.eq(q.field("name"), name)) 10 | .order("desc") 11 | .first(); 12 | if (file === null) { 13 | return null; 14 | } 15 | const files = await Promise.all( 16 | file.uploaded.map(async ({ storageId, type }) => ({ 17 | url: await ctx.storage.getUrl(storageId), 18 | type, 19 | })) 20 | ); 21 | return { files }; 22 | }, 23 | }); 24 | 25 | export const generateUploadUrl = mutation({ 26 | args: { name: v.string() }, 27 | handler: async (ctx, { name }) => { 28 | if (name !== name.toLowerCase()) { 29 | throw new Error("Unauthenticated"); 30 | } 31 | return await ctx.storage.generateUploadUrl(); 32 | }, 33 | }); 34 | 35 | export const attachUploaded = mutation({ 36 | args: { 37 | name: v.string(), 38 | uploaded: v.array( 39 | v.object({ 40 | storageId: v.string(), 41 | type: v.string(), 42 | }) 43 | ), 44 | }, 45 | handler: async (ctx, { name, uploaded }) => { 46 | await ctx.db.insert("files", { name, uploaded }); 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /examples/simple/convex/schema.ts: -------------------------------------------------------------------------------- 1 | import { defineSchema, defineTable } from "convex/server"; 2 | import { v } from "convex/values"; 3 | 4 | export default defineSchema({ 5 | files: defineTable({ 6 | name: v.string(), 7 | uploaded: v.array( 8 | v.object({ 9 | storageId: v.string(), 10 | type: v.string(), 11 | }) 12 | ), 13 | }), 14 | }); 15 | -------------------------------------------------------------------------------- /examples/simple/convex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* This TypeScript project config describes the environment that 3 | * Convex functions run in and is used to typecheck them. 4 | * You can modify it, but some settings required to use Convex. 5 | */ 6 | "compilerOptions": { 7 | /* These settings are not required by Convex and can be modified. */ 8 | "allowJs": true, 9 | "strict": true, 10 | 11 | /* These compiler options are required by Convex */ 12 | "target": "ESNext", 13 | "lib": ["ES2021", "dom"], 14 | "forceConsistentCasingInFileNames": true, 15 | "allowSyntheticDefaultImports": true, 16 | "module": "ESNext", 17 | "moduleResolution": "Node", 18 | "isolatedModules": true, 19 | "noEmit": true 20 | }, 21 | "include": ["./**/*"], 22 | "exclude": ["./_generated"] 23 | } 24 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/simple", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "convex": "^1.6.3", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "tailwind-merge": "^1.14.0", 17 | "uploadstuff": "file:../.." 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.2.15", 21 | "@types/react-dom": "^18.2.7", 22 | "@typescript-eslint/eslint-plugin": "^6.0.0", 23 | "@typescript-eslint/parser": "^6.0.0", 24 | "@vitejs/plugin-react": "^4.0.3", 25 | "autoprefixer": "^10.4.15", 26 | "eslint": "^8.45.0", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.4.3", 29 | "postcss": "^8.4.29", 30 | "tailwindcss": "^3.3.3", 31 | "typescript": "^5.0.2", 32 | "vite": "^4.4.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/simple/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/simple/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0 auto; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | 9 | main { 10 | width: 100%; 11 | height: 100%; 12 | display: grid; 13 | grid-template-columns: repeat(2, 1fr); 14 | gap: 2rem; 15 | padding: 2rem; 16 | } 17 | 18 | main > div { 19 | height: 100%; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | gap: 1rem; 25 | } 26 | 27 | .img { 28 | position: relative; 29 | flex-grow: 1; 30 | display: block; 31 | width: 100%; 32 | } 33 | 34 | main img { 35 | top: 50%; 36 | left: 50%; 37 | transform: translate(-50%, -50%); 38 | position: absolute; 39 | max-width: 100%; 40 | max-height: 100%; 41 | object-fit: contain; 42 | } 43 | -------------------------------------------------------------------------------- /examples/simple/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from "react"; 2 | import "./App.css"; 3 | import { useMutation, useQuery } from "convex/react"; 4 | import { api } from "../convex/_generated/api"; 5 | import { 6 | UploadButton, 7 | UploadDropzone, 8 | UploadFileResponse, 9 | } from "uploadstuff/react"; 10 | import "uploadstuff/react/styles.css"; 11 | 12 | export default function App() { 13 | const [name, setName] = useState(""); 14 | const generateUploadUrl = useMutation(api.files.generateUploadUrl); 15 | const attachUploaded = useMutation(api.files.attachUploaded); 16 | const attachToName = async (uploaded: UploadFileResponse[]) => { 17 | attachUploaded({ 18 | name, 19 | uploaded: uploaded.map(({ type, response }) => ({ 20 | type, 21 | storageId: (response as { storageId: string }).storageId, 22 | })), 23 | }); 24 | }; 25 | 26 | return ( 27 |
28 |
29 |
30 | Pick a name to store files under:{" "} 31 | setName(e.target.value)} /> 32 |
33 | generateUploadUrl({ name })} 35 | multiple={true} 36 | onUploadComplete={attachToName} 37 | onUploadError={console.error} 38 | /> 39 | Or 40 | generateUploadUrl({ name })} 42 | multiple={true} 43 | onUploadComplete={attachToName} 44 | onUploadError={console.error} 45 | /> 46 |
47 |
48 | 49 |
50 |
51 | ); 52 | } 53 | 54 | function Display({ name }: { name: string }) { 55 | const uploaded = useQuery(api.files.getUrl, { name }); 56 | if (uploaded == null) { 57 | return null; 58 | } 59 | return uploaded.files.map(({ url, type }) => ( 60 | 61 | {type.startsWith("image") ? ( 62 |
63 | 64 |
65 | ) : ( 66 | Download 67 | )} 68 |
69 | )); 70 | } 71 | -------------------------------------------------------------------------------- /examples/simple/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | :root { 10 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 11 | line-height: 1.5; 12 | font-weight: 400; 13 | 14 | color-scheme: light dark; 15 | color: rgba(255, 255, 255, 0.87); 16 | background-color: #242424; 17 | 18 | font-synthesis: none; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | -webkit-text-size-adjust: 100%; 23 | } 24 | 25 | html, 26 | body { 27 | width: 100%; 28 | height: 100%; 29 | } 30 | 31 | body { 32 | margin: 0; 33 | display: flex; 34 | place-items: center; 35 | min-width: 320px; 36 | min-height: 100vh; 37 | } 38 | 39 | input { 40 | padding: 8px; 41 | border-radius: 8px; 42 | border: 1px solid black; 43 | } 44 | 45 | @media (prefers-color-scheme: light) { 46 | :root { 47 | color: #213547; 48 | background-color: #ffffff; 49 | } 50 | a:hover { 51 | color: #747bff; 52 | } 53 | button { 54 | background-color: #f9f9f9; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/simple/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | import { ConvexProvider, ConvexReactClient } from "convex/react"; 6 | 7 | const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); 8 | 9 | ReactDOM.createRoot(document.getElementById("root")!).render( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /examples/simple/src/useEvent.ts: -------------------------------------------------------------------------------- 1 | // Ripped from https://github.com/scottrippey/react-use-event-hook 2 | import React from "react"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | type AnyFunction = (...args: any[]) => any; 6 | const noop = () => void 0; 7 | 8 | /** 9 | * Suppress the warning when using useLayoutEffect with SSR. (https://reactjs.org/link/uselayouteffect-ssr) 10 | * Make use of useInsertionEffect if available. 11 | */ 12 | const useInsertionEffect = 13 | typeof window !== "undefined" 14 | ? // useInsertionEffect is available in React 18+ 15 | React.useInsertionEffect || React.useLayoutEffect 16 | : noop; 17 | 18 | /** 19 | * Similar to useCallback, with a few subtle differences: 20 | * - The returned function is a stable reference, and will always be the same between renders 21 | * - No dependency lists required 22 | * - Properties or state accessed within the callback will always be "current" 23 | */ 24 | export function useEvent( 25 | callback: TCallback 26 | ): TCallback { 27 | // Keep track of the latest callback: 28 | const latestRef = React.useRef( 29 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any 30 | useEvent_shouldNotBeInvokedBeforeMount as any 31 | ); 32 | useInsertionEffect(() => { 33 | latestRef.current = callback; 34 | }, [callback]); 35 | 36 | // Create a stable callback that always calls the latest callback: 37 | // using useRef instead of useCallback avoids creating and empty array on every render 38 | const stableRef = React.useRef(); 39 | if (!stableRef.current) { 40 | stableRef.current = function (this: unknown) { 41 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return, prefer-rest-params, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any 42 | return latestRef.current.apply(this, arguments as any); 43 | } as TCallback; 44 | } 45 | 46 | return stableRef.current; 47 | } 48 | 49 | /** 50 | * Render methods should be pure, especially when concurrency is used, 51 | * so we will throw this error if the callback is called while rendering. 52 | */ 53 | function useEvent_shouldNotBeInvokedBeforeMount() { 54 | throw new Error( 55 | "INVALID_USEEVENT_INVOCATION: the callback from useEvent cannot be invoked before the component has mounted." 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /examples/simple/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/simple/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /examples/simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/simple/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/simple/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /lib/UploadButton.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { UploadSpinner } from "./UploadSpinner"; 4 | import { UploadFileResponse } from "./uploadFiles"; 5 | import { useUploadFiles } from "./useUploadFiles"; 6 | 7 | export function UploadButton(props: { 8 | /// Required props 9 | 10 | // Either the absolute upload URL or an async function that generates it 11 | uploadUrl: string | (() => Promise); 12 | 13 | /// Optional functionality props 14 | 15 | // A list of [file type specifiers]((https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers)) 16 | fileTypes?: string[]; 17 | // Whether the user can select multiple files to upload. Defaults to `false` 18 | multiple?: boolean; 19 | 20 | /// Optional lifecycle props 21 | 22 | // Called every time the combined upload progresses by at least 10 percent. `progress` % is a multiple of 10. 23 | onUploadProgress?: (progress: number) => void; 24 | // Called at the start of each upload. 25 | onUploadBegin?: (fileName: string) => void; 26 | // Called when all the files have been uploaded. 27 | onUploadComplete?: (uploaded: UploadFileResponse[]) => Promise | void; 28 | // Called if there was an error at any point in the upload process. 29 | onUploadError?: (error: unknown) => void; 30 | 31 | /// Optional appearance props 32 | 33 | // Replaces the content shown on the button. `progress` % is a multiple of 10 if the upload is in progress or `null`. 34 | content?: (progress: number | null) => string; 35 | // Replaces the `className` of the button. `progress` % is a multiple of 10 if the upload is in progress or `null`. 36 | className?: (progress: number | null) => string; 37 | }) { 38 | const fileInputRef = useRef(null); 39 | const [uploadProgress, setUploadProgress] = useState(0); 40 | const { startUpload, isUploading } = useUploadFiles(props.uploadUrl, { 41 | onUploadComplete: async (res) => { 42 | if (fileInputRef.current) { 43 | fileInputRef.current.value = ""; 44 | } 45 | await props.onUploadComplete?.(res); 46 | setUploadProgress(0); 47 | }, 48 | onUploadProgress: (p) => { 49 | setUploadProgress(p); 50 | props.onUploadProgress?.(p); 51 | }, 52 | onUploadError: props.onUploadError, 53 | onUploadBegin: props.onUploadBegin, 54 | }); 55 | 56 | const combinedState = isUploading ? uploadProgress : null; 57 | 58 | return ( 59 | 95 | ); 96 | } 97 | 98 | const progressWidths: Record = { 99 | 0: "after:w-0", 100 | 10: "after:w-[10%]", 101 | 20: "after:w-[20%]", 102 | 30: "after:w-[30%]", 103 | 40: "after:w-[40%]", 104 | 50: "after:w-[50%]", 105 | 60: "after:w-[60%]", 106 | 70: "after:w-[70%]", 107 | 80: "after:w-[80%]", 108 | 90: "after:w-[90%]", 109 | 100: "after:w-[100%]", 110 | }; 111 | -------------------------------------------------------------------------------- /lib/UploadDropzone.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import type { Accept, FileWithPath } from "react-dropzone"; 3 | import { useDropzone } from "react-dropzone"; 4 | import { twMerge } from "tailwind-merge"; 5 | import { UploadFileResponse } from "."; 6 | import { useUploadFiles } from "./useUploadFiles"; 7 | import { UploadSpinner } from "./UploadSpinner"; 8 | 9 | type UploadDropzoneState = { 10 | progress: number | null; 11 | isDragActive: boolean; 12 | }; 13 | 14 | export function UploadDropzone(props: { 15 | /// Required props 16 | 17 | // Either the absolute upload URL or an async function that generates it 18 | uploadUrl: string | (() => Promise); 19 | 20 | /// Optional functionality props 21 | 22 | // An object of with a common [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) as keys and an array of file extensions as values (similar to [showOpenFilePicker](https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker)'s types accept option) 23 | fileTypes?: Accept; 24 | // Whether the user can select multiple files to upload. Defaults to `false` 25 | multiple?: boolean; 26 | // Whether the upload should start right after the user drags the file in. Defaults to `false` 27 | uploadImmediately?: boolean; 28 | 29 | /// Optional life-cycle props 30 | 31 | // Called every time the combined upload progresses by at least 10 percent. `progress` % is a multiple of 10. 32 | onUploadProgress?: (progress: number) => void; 33 | // Called at the start of each upload. 34 | onUploadBegin?: (fileName: string) => void; 35 | // Called when all the files have been uploaded. 36 | onUploadComplete?: (uploaded: UploadFileResponse[]) => Promise | void; 37 | // Called if there was an error at any point in the upload process. 38 | onUploadError?: (error: unknown) => void; 39 | 40 | /// Optional appearance props 41 | 42 | // Text, if provided, is shown below the "Choose files" line 43 | subtitle?: string; 44 | // Replaces all of the content shown in the dropzone. `progress` % is a multiple of 10 if the upload is in progress or `null`. 45 | content?: (state: UploadDropzoneState) => string; 46 | // Replaces the `className` of the dropzone. `progress` % is a multiple of 10 if the upload is in progress or `null`. 47 | className?: (state: UploadDropzoneState) => string; 48 | }) { 49 | const [files, setFiles] = useState([]); 50 | 51 | const [uploadProgress, setUploadProgress] = useState(0); 52 | const { startUpload, isUploading } = useUploadFiles(props.uploadUrl, { 53 | onUploadComplete: async (res) => { 54 | setFiles([]); 55 | await props.onUploadComplete?.(res); 56 | setUploadProgress(0); 57 | }, 58 | onUploadProgress: (p) => { 59 | setUploadProgress(p); 60 | props.onUploadProgress?.(p); 61 | }, 62 | onUploadError: props.onUploadError, 63 | onUploadBegin: props.onUploadBegin, 64 | }); 65 | 66 | const onDrop = useCallback( 67 | (acceptedFiles: FileWithPath[]) => { 68 | setFiles(acceptedFiles); 69 | 70 | if (props.uploadImmediately === true) { 71 | void startUpload(acceptedFiles); 72 | return; 73 | } 74 | }, 75 | [props, startUpload] 76 | ); 77 | 78 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 79 | onDrop, 80 | accept: props.fileTypes, 81 | disabled: false, 82 | }); 83 | 84 | const onUploadClick = ( 85 | e: React.MouseEvent 86 | ) => { 87 | e.preventDefault(); 88 | e.stopPropagation(); 89 | if (files.length === 0) { 90 | return; 91 | } 92 | 93 | void startUpload(files); 94 | }; 95 | 96 | const combinedState = { 97 | isDragActive, 98 | progress: isUploading ? uploadProgress : null, 99 | }; 100 | 101 | return ( 102 |
113 | {props.content?.(combinedState) ?? ( 114 | 121 | 127 | 128 | )} 129 | 139 | {props.subtitle !== undefined ? ( 140 |
145 | {props.subtitle} 146 |
147 | ) : null} 148 | {files.length > 0 ? ( 149 | 166 | ) : null} 167 |
168 | ); 169 | } 170 | 171 | const progressWidths: Record = { 172 | 0: "after:w-0", 173 | 10: "after:w-[10%]", 174 | 20: "after:w-[20%]", 175 | 30: "after:w-[30%]", 176 | 40: "after:w-[40%]", 177 | 50: "after:w-[50%]", 178 | 60: "after:w-[60%]", 179 | 70: "after:w-[70%]", 180 | 80: "after:w-[80%]", 181 | 90: "after:w-[90%]", 182 | 100: "after:w-[100%]", 183 | }; 184 | -------------------------------------------------------------------------------- /lib/UploadSpinner.tsx: -------------------------------------------------------------------------------- 1 | export function UploadSpinner() { 2 | return ( 3 | 9 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./uploadFiles"; 2 | -------------------------------------------------------------------------------- /lib/react.ts: -------------------------------------------------------------------------------- 1 | export * from "./UploadButton"; 2 | export * from "./UploadDropzone"; 3 | export * from "./UploadSpinner"; 4 | export * from "./useUploadFiles"; 5 | export type { UploadFileResponse } from "./uploadFiles"; 6 | -------------------------------------------------------------------------------- /lib/styles.css: -------------------------------------------------------------------------------- 1 | .focus-within\:ring-2 { 2 | --tw-ring-inset: ; 3 | --tw-ring-offset-width: 0px; 4 | --tw-ring-offset-color: #fff; 5 | --tw-ring-color: rgb(59 130 246 / 0.5); 6 | --tw-ring-offset-shadow: 0 0 #0000; 7 | --tw-ring-shadow: 0 0 #0000; 8 | --tw-shadow: 0 0 #0000; 9 | --tw-shadow-colored: 0 0 #0000 10 | } 11 | 12 | .sr-only { 13 | position: absolute; 14 | width: 1px; 15 | height: 1px; 16 | padding: 0; 17 | margin: -1px; 18 | overflow: hidden; 19 | clip: rect(0, 0, 0, 0); 20 | white-space: nowrap; 21 | border-width: 0 22 | } 23 | 24 | .absolute { 25 | position: absolute 26 | } 27 | 28 | .relative { 29 | position: relative 30 | } 31 | 32 | .m-0 { 33 | margin: 0px 34 | } 35 | 36 | .mx-auto { 37 | margin-left: auto; 38 | margin-right: auto 39 | } 40 | 41 | .mt-4 { 42 | margin-top: 1rem 43 | } 44 | 45 | .block { 46 | display: block 47 | } 48 | 49 | .flex { 50 | display: flex 51 | } 52 | 53 | .hidden { 54 | display: none 55 | } 56 | 57 | .h-10 { 58 | height: 2.5rem 59 | } 60 | 61 | .h-12 { 62 | height: 3rem 63 | } 64 | 65 | .h-5 { 66 | height: 1.25rem 67 | } 68 | 69 | .h-\[1\.25rem\] { 70 | height: 1.25rem 71 | } 72 | 73 | .w-12 { 74 | width: 3rem 75 | } 76 | 77 | .w-36 { 78 | width: 9rem 79 | } 80 | 81 | .w-5 { 82 | width: 1.25rem 83 | } 84 | 85 | .w-64 { 86 | width: 16rem 87 | } 88 | 89 | @keyframes spin { 90 | to { 91 | transform: rotate(360deg) 92 | } 93 | } 94 | 95 | .animate-spin { 96 | animation: spin 1s linear infinite 97 | } 98 | 99 | .cursor-pointer { 100 | cursor: pointer 101 | } 102 | 103 | .flex-col { 104 | flex-direction: column 105 | } 106 | 107 | .items-center { 108 | align-items: center 109 | } 110 | 111 | .justify-center { 112 | justify-content: center 113 | } 114 | 115 | .overflow-hidden { 116 | overflow: hidden 117 | } 118 | 119 | .rounded-lg { 120 | border-radius: 0.5rem 121 | } 122 | 123 | .rounded-md { 124 | border-radius: 0.375rem 125 | } 126 | 127 | .border { 128 | border-width: 1px 129 | } 130 | 131 | .border-dashed { 132 | border-style: dashed 133 | } 134 | 135 | .border-gray-900\/25 { 136 | border-color: rgb(17 24 39 / 0.25) 137 | } 138 | 139 | .bg-blue-400 { 140 | --tw-bg-opacity: 1; 141 | background-color: rgb(96 165 250 / var(--tw-bg-opacity)) 142 | } 143 | 144 | .bg-blue-600 { 145 | --tw-bg-opacity: 1; 146 | background-color: rgb(37 99 235 / var(--tw-bg-opacity)) 147 | } 148 | 149 | .bg-blue-600\/10 { 150 | background-color: rgb(37 99 235 / 0.1) 151 | } 152 | 153 | .px-6 { 154 | padding-left: 1.5rem; 155 | padding-right: 1.5rem 156 | } 157 | 158 | .py-10 { 159 | padding-top: 2.5rem; 160 | padding-bottom: 2.5rem 161 | } 162 | 163 | .py-\[4\.25rem\] { 164 | padding-top: 4.25rem; 165 | padding-bottom: 4.25rem 166 | } 167 | 168 | .text-center { 169 | text-align: center 170 | } 171 | 172 | .align-middle { 173 | vertical-align: middle 174 | } 175 | 176 | .text-sm { 177 | font-size: 0.875rem; 178 | line-height: 1.25rem 179 | } 180 | 181 | .text-xs { 182 | font-size: 0.75rem; 183 | line-height: 1rem 184 | } 185 | 186 | .font-semibold { 187 | font-weight: 600 188 | } 189 | 190 | .leading-5 { 191 | line-height: 1.25rem 192 | } 193 | 194 | .leading-6 { 195 | line-height: 1.5rem 196 | } 197 | 198 | .text-blue-600 { 199 | --tw-text-opacity: 1; 200 | color: rgb(37 99 235 / var(--tw-text-opacity)) 201 | } 202 | 203 | .text-gray-400 { 204 | --tw-text-opacity: 1; 205 | color: rgb(156 163 175 / var(--tw-text-opacity)) 206 | } 207 | 208 | .text-gray-600 { 209 | --tw-text-opacity: 1; 210 | color: rgb(75 85 99 / var(--tw-text-opacity)) 211 | } 212 | 213 | .text-white { 214 | --tw-text-opacity: 1; 215 | color: rgb(255 255 255 / var(--tw-text-opacity)) 216 | } 217 | 218 | .before\:absolute::before { 219 | content: var(--tw-content); 220 | position: absolute 221 | } 222 | 223 | .before\:-z-20::before { 224 | content: var(--tw-content); 225 | z-index: -20 226 | } 227 | 228 | .before\:h-full::before { 229 | content: var(--tw-content); 230 | height: 100% 231 | } 232 | 233 | .before\:w-full::before { 234 | content: var(--tw-content); 235 | width: 100% 236 | } 237 | 238 | .before\:bg-blue-400::before { 239 | content: var(--tw-content); 240 | --tw-bg-opacity: 1; 241 | background-color: rgb(96 165 250 / var(--tw-bg-opacity)) 242 | } 243 | 244 | .after\:absolute::after { 245 | content: var(--tw-content); 246 | position: absolute 247 | } 248 | 249 | .after\:left-0::after { 250 | content: var(--tw-content); 251 | left: 0px 252 | } 253 | 254 | .after\:-z-10::after { 255 | content: var(--tw-content); 256 | z-index: -10 257 | } 258 | 259 | .after\:h-full::after { 260 | content: var(--tw-content); 261 | height: 100% 262 | } 263 | 264 | .after\:w-0::after { 265 | content: var(--tw-content); 266 | width: 0px 267 | } 268 | 269 | .after\:w-\[10\%\]::after { 270 | content: var(--tw-content); 271 | width: 10% 272 | } 273 | 274 | .after\:w-\[100\%\]::after { 275 | content: var(--tw-content); 276 | width: 100% 277 | } 278 | 279 | .after\:w-\[20\%\]::after { 280 | content: var(--tw-content); 281 | width: 20% 282 | } 283 | 284 | .after\:w-\[30\%\]::after { 285 | content: var(--tw-content); 286 | width: 30% 287 | } 288 | 289 | .after\:w-\[40\%\]::after { 290 | content: var(--tw-content); 291 | width: 40% 292 | } 293 | 294 | .after\:w-\[50\%\]::after { 295 | content: var(--tw-content); 296 | width: 50% 297 | } 298 | 299 | .after\:w-\[60\%\]::after { 300 | content: var(--tw-content); 301 | width: 60% 302 | } 303 | 304 | .after\:w-\[70\%\]::after { 305 | content: var(--tw-content); 306 | width: 70% 307 | } 308 | 309 | .after\:w-\[80\%\]::after { 310 | content: var(--tw-content); 311 | width: 80% 312 | } 313 | 314 | .after\:w-\[90\%\]::after { 315 | content: var(--tw-content); 316 | width: 90% 317 | } 318 | 319 | .after\:bg-blue-600::after { 320 | content: var(--tw-content); 321 | --tw-bg-opacity: 1; 322 | background-color: rgb(37 99 235 / var(--tw-bg-opacity)) 323 | } 324 | 325 | .after\:transition-\[width\]::after { 326 | content: var(--tw-content); 327 | transition-property: width; 328 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 329 | transition-duration: 150ms 330 | } 331 | 332 | .after\:duration-500::after { 333 | content: var(--tw-content); 334 | transition-duration: 500ms 335 | } 336 | 337 | .focus-within\:outline-none:focus-within { 338 | outline: 2px solid transparent; 339 | outline-offset: 2px 340 | } 341 | 342 | .focus-within\:ring-2:focus-within { 343 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); 344 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); 345 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) 346 | } 347 | 348 | .focus-within\:ring-blue-600:focus-within { 349 | --tw-ring-opacity: 1; 350 | --tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity)) 351 | } 352 | 353 | .focus-within\:ring-offset-2:focus-within { 354 | --tw-ring-offset-width: 2px 355 | } 356 | 357 | .hover\:bg-blue-600\/90:hover { 358 | background-color: rgb(37 99 235 / 0.9) 359 | } 360 | 361 | .hover\:text-blue-500:hover { 362 | --tw-text-opacity: 1; 363 | color: rgb(59 130 246 / var(--tw-text-opacity)) 364 | } 365 | 366 | @media (prefers-color-scheme: dark) { 367 | .dark\:border-gray-200\/25 { 368 | border-color: rgb(229 231 235 / 0.25) 369 | } 370 | 371 | .dark\:text-gray-300 { 372 | --tw-text-opacity: 1; 373 | color: rgb(209 213 219 / var(--tw-text-opacity)) 374 | } 375 | 376 | .dark\:text-gray-500 { 377 | --tw-text-opacity: 1; 378 | color: rgb(107 114 128 / var(--tw-text-opacity)) 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /lib/uploadFiles.ts: -------------------------------------------------------------------------------- 1 | export type UploadFileResponse = { 2 | name: string; 3 | type: string; 4 | size: number; 5 | response: unknown; 6 | }; 7 | 8 | export const uploadFiles = async (args: { 9 | url: string; 10 | files: File[]; 11 | onUploadBegin?: ({ file }: { file: string }) => void; 12 | onUploadProgress?: ({ 13 | file, 14 | progress, 15 | }: { 16 | file: string; 17 | progress: number; 18 | }) => void; 19 | }): Promise => { 20 | return Promise.all( 21 | args.files.map(async (file: File) => { 22 | const response = await fetchWithProgress( 23 | args.url, 24 | { 25 | method: "POST", 26 | body: file, 27 | headers: new Headers({ 28 | "Content-Type": getMimeType(file), 29 | }), 30 | }, 31 | (progressEvent) => 32 | args.onUploadProgress?.({ 33 | file: file.name, 34 | progress: (progressEvent.loaded / progressEvent.total) * 100, 35 | }), 36 | () => { 37 | args.onUploadBegin?.({ 38 | file: file.name, 39 | }); 40 | } 41 | ); 42 | 43 | return { 44 | name: file.name, 45 | size: file.size, 46 | type: file.type, 47 | response, 48 | }; 49 | }) 50 | ); 51 | }; 52 | 53 | function fetchWithProgress( 54 | url: string, 55 | opts: { 56 | headers?: Headers; 57 | method?: string; 58 | body?: File; 59 | } = {}, 60 | onProgress?: (this: XMLHttpRequest, progress: ProgressEvent) => void, 61 | onUploadBegin?: (this: XMLHttpRequest, progress: ProgressEvent) => void 62 | ): Promise { 63 | return new Promise((resolve, reject) => { 64 | const xhr = new XMLHttpRequest(); 65 | xhr.responseType = "json"; 66 | xhr.open(opts.method ?? "get", url); 67 | opts.headers && 68 | Object.keys(opts.headers).forEach( 69 | (h) => 70 | opts.headers && xhr.setRequestHeader(h, opts.headers.get(h) ?? "") 71 | ); 72 | xhr.onload = () => { 73 | resolve(xhr.response); 74 | }; 75 | 76 | xhr.onerror = reject; 77 | if (xhr.upload && onProgress) xhr.upload.onprogress = onProgress; 78 | if (xhr.upload && onUploadBegin) xhr.upload.onloadstart = onUploadBegin; 79 | xhr.send(opts.body); 80 | }); 81 | } 82 | 83 | function getMimeType(file: File) { 84 | if (file.type === "blob") { 85 | return "application/octet-stream"; 86 | } else if (file.type === "pdf") { 87 | return "application/pdf"; 88 | } else { 89 | return file.type; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/useEvent.ts: -------------------------------------------------------------------------------- 1 | // Ripped from https://github.com/scottrippey/react-use-event-hook 2 | import { useInsertionEffect, useLayoutEffect, useRef } from "react"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | type AnyFunction = (...args: any[]) => any; 6 | const noop = () => void 0; 7 | 8 | /** 9 | * Suppress the warning when using useLayoutEffect with SSR. (https://reactjs.org/link/uselayouteffect-ssr) 10 | * Make use of useInsertionEffect if available. 11 | */ 12 | const useInsertionEffect_ = 13 | typeof window !== "undefined" 14 | ? // useInsertionEffect is available in React 18+ 15 | useInsertionEffect || useLayoutEffect 16 | : noop; 17 | 18 | /** 19 | * Similar to useCallback, with a few subtle differences: 20 | * - The returned function is a stable reference, and will always be the same between renders 21 | * - No dependency lists required 22 | * - Properties or state accessed within the callback will always be "current" 23 | */ 24 | export function useEvent( 25 | callback: TCallback 26 | ): TCallback { 27 | // Keep track of the latest callback: 28 | const latestRef = useRef( 29 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any 30 | useEvent_shouldNotBeInvokedBeforeMount as any 31 | ); 32 | useInsertionEffect_(() => { 33 | latestRef.current = callback; 34 | }, [callback]); 35 | 36 | // Create a stable callback that always calls the latest callback: 37 | // using useRef instead of useCallback avoids creating and empty array on every render 38 | const stableRef = useRef(); 39 | if (!stableRef.current) { 40 | stableRef.current = function (this: unknown) { 41 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return, prefer-rest-params, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any 42 | return latestRef.current.apply(this, arguments as any); 43 | } as TCallback; 44 | } 45 | 46 | return stableRef.current; 47 | } 48 | 49 | /** 50 | * Render methods should be pure, especially when concurrency is used, 51 | * so we will throw this error if the callback is called while rendering. 52 | */ 53 | function useEvent_shouldNotBeInvokedBeforeMount() { 54 | throw new Error( 55 | "INVALID_USEEVENT_INVOCATION: the callback from useEvent cannot be invoked before the component has mounted." 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /lib/useUploadFiles.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import { useEvent } from "./useEvent"; 3 | import { UploadFileResponse, uploadFiles } from "./uploadFiles"; 4 | 5 | export const useUploadFiles = ( 6 | uploadUrl: string | (() => Promise), 7 | opts?: { 8 | onUploadComplete?: (res: UploadFileResponse[]) => Promise; 9 | onUploadProgress?: (p: number) => void; 10 | onUploadError?: (e: unknown) => void; 11 | onUploadBegin?: (fileName: string) => void; 12 | } 13 | ): { 14 | startUpload: (files: File[]) => Promise; 15 | isUploading: boolean; 16 | } => { 17 | const [isUploading, setUploading] = useState(false); 18 | const uploadProgress = useRef(0); 19 | const fileProgress = useRef>(new Map()); 20 | 21 | const startUpload = useEvent(async (files: File[]) => { 22 | setUploading(true); 23 | 24 | try { 25 | const url = typeof uploadUrl === "string" ? uploadUrl : await uploadUrl(); 26 | const res = await uploadFiles({ 27 | files, 28 | url, 29 | onUploadProgress: ({ file, progress }) => { 30 | if (opts?.onUploadProgress == null) { 31 | return; 32 | } 33 | fileProgress.current.set(file, progress); 34 | let sum = 0; 35 | fileProgress.current.forEach((singleFileProgress) => { 36 | sum += singleFileProgress; 37 | }); 38 | const averageProgress = 39 | Math.floor(sum / fileProgress.current.size / 10) * 10; 40 | if (averageProgress !== uploadProgress.current) { 41 | opts?.onUploadProgress?.(averageProgress); 42 | uploadProgress.current = averageProgress; 43 | } 44 | }, 45 | onUploadBegin({ file }) { 46 | opts?.onUploadBegin?.(file); 47 | }, 48 | }); 49 | 50 | await opts?.onUploadComplete?.(res); 51 | return res; 52 | } catch (error) { 53 | opts?.onUploadError?.(error); 54 | return []; 55 | } finally { 56 | setUploading(false); 57 | fileProgress.current = new Map(); 58 | uploadProgress.current = 0; 59 | } 60 | }); 61 | 62 | return { 63 | startUpload, 64 | isUploading, 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xixixao/uploadstuff", 3 | "version": "0.0.5", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@xixixao/uploadstuff", 9 | "version": "0.0.5", 10 | "license": "MIT", 11 | "dependencies": { 12 | "react-dom": "^18.3.1", 13 | "tailwind-merge": "^1.14.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.2.21", 17 | "react-dropzone": "^14.2.3", 18 | "tailwindcss": "^3.3.3" 19 | }, 20 | "peerDependencies": { 21 | "react": "^18.2.0", 22 | "react-dropzone": "^14.2.3", 23 | "typescript": "^5.0.0" 24 | } 25 | }, 26 | "node_modules/@alloc/quick-lru": { 27 | "version": "5.2.0", 28 | "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", 29 | "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", 30 | "dev": true, 31 | "license": "MIT", 32 | "engines": { 33 | "node": ">=10" 34 | }, 35 | "funding": { 36 | "url": "https://github.com/sponsors/sindresorhus" 37 | } 38 | }, 39 | "node_modules/@isaacs/cliui": { 40 | "version": "8.0.2", 41 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 42 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 43 | "dev": true, 44 | "license": "ISC", 45 | "dependencies": { 46 | "string-width": "^5.1.2", 47 | "string-width-cjs": "npm:string-width@^4.2.0", 48 | "strip-ansi": "^7.0.1", 49 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 50 | "wrap-ansi": "^8.1.0", 51 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 52 | }, 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@jridgewell/gen-mapping": { 58 | "version": "0.3.5", 59 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 60 | "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 61 | "dev": true, 62 | "license": "MIT", 63 | "dependencies": { 64 | "@jridgewell/set-array": "^1.2.1", 65 | "@jridgewell/sourcemap-codec": "^1.4.10", 66 | "@jridgewell/trace-mapping": "^0.3.24" 67 | }, 68 | "engines": { 69 | "node": ">=6.0.0" 70 | } 71 | }, 72 | "node_modules/@jridgewell/resolve-uri": { 73 | "version": "3.1.2", 74 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 75 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 76 | "dev": true, 77 | "license": "MIT", 78 | "engines": { 79 | "node": ">=6.0.0" 80 | } 81 | }, 82 | "node_modules/@jridgewell/set-array": { 83 | "version": "1.2.1", 84 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 85 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 86 | "dev": true, 87 | "license": "MIT", 88 | "engines": { 89 | "node": ">=6.0.0" 90 | } 91 | }, 92 | "node_modules/@jridgewell/sourcemap-codec": { 93 | "version": "1.5.0", 94 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 95 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 96 | "dev": true, 97 | "license": "MIT" 98 | }, 99 | "node_modules/@jridgewell/trace-mapping": { 100 | "version": "0.3.25", 101 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 102 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 103 | "dev": true, 104 | "license": "MIT", 105 | "dependencies": { 106 | "@jridgewell/resolve-uri": "^3.1.0", 107 | "@jridgewell/sourcemap-codec": "^1.4.14" 108 | } 109 | }, 110 | "node_modules/@nodelib/fs.scandir": { 111 | "version": "2.1.5", 112 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 113 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 114 | "dev": true, 115 | "license": "MIT", 116 | "dependencies": { 117 | "@nodelib/fs.stat": "2.0.5", 118 | "run-parallel": "^1.1.9" 119 | }, 120 | "engines": { 121 | "node": ">= 8" 122 | } 123 | }, 124 | "node_modules/@nodelib/fs.stat": { 125 | "version": "2.0.5", 126 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 127 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 128 | "dev": true, 129 | "license": "MIT", 130 | "engines": { 131 | "node": ">= 8" 132 | } 133 | }, 134 | "node_modules/@nodelib/fs.walk": { 135 | "version": "1.2.8", 136 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 137 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 138 | "dev": true, 139 | "license": "MIT", 140 | "dependencies": { 141 | "@nodelib/fs.scandir": "2.1.5", 142 | "fastq": "^1.6.0" 143 | }, 144 | "engines": { 145 | "node": ">= 8" 146 | } 147 | }, 148 | "node_modules/@pkgjs/parseargs": { 149 | "version": "0.11.0", 150 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 151 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 152 | "dev": true, 153 | "license": "MIT", 154 | "optional": true, 155 | "engines": { 156 | "node": ">=14" 157 | } 158 | }, 159 | "node_modules/@types/prop-types": { 160 | "version": "15.7.13", 161 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", 162 | "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", 163 | "dev": true, 164 | "license": "MIT" 165 | }, 166 | "node_modules/@types/react": { 167 | "version": "18.3.12", 168 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", 169 | "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", 170 | "dev": true, 171 | "license": "MIT", 172 | "dependencies": { 173 | "@types/prop-types": "*", 174 | "csstype": "^3.0.2" 175 | } 176 | }, 177 | "node_modules/ansi-regex": { 178 | "version": "6.1.0", 179 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", 180 | "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", 181 | "dev": true, 182 | "license": "MIT", 183 | "engines": { 184 | "node": ">=12" 185 | }, 186 | "funding": { 187 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 188 | } 189 | }, 190 | "node_modules/ansi-styles": { 191 | "version": "6.2.1", 192 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 193 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 194 | "dev": true, 195 | "license": "MIT", 196 | "engines": { 197 | "node": ">=12" 198 | }, 199 | "funding": { 200 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 201 | } 202 | }, 203 | "node_modules/any-promise": { 204 | "version": "1.3.0", 205 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 206 | "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", 207 | "dev": true, 208 | "license": "MIT" 209 | }, 210 | "node_modules/anymatch": { 211 | "version": "3.1.3", 212 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 213 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 214 | "dev": true, 215 | "license": "ISC", 216 | "dependencies": { 217 | "normalize-path": "^3.0.0", 218 | "picomatch": "^2.0.4" 219 | }, 220 | "engines": { 221 | "node": ">= 8" 222 | } 223 | }, 224 | "node_modules/arg": { 225 | "version": "5.0.2", 226 | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 227 | "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", 228 | "dev": true, 229 | "license": "MIT" 230 | }, 231 | "node_modules/attr-accept": { 232 | "version": "2.2.4", 233 | "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.4.tgz", 234 | "integrity": "sha512-2pA6xFIbdTUDCAwjN8nQwI+842VwzbDUXO2IYlpPXQIORgKnavorcr4Ce3rwh+zsNg9zK7QPsdvDj3Lum4WX4w==", 235 | "dev": true, 236 | "license": "MIT", 237 | "engines": { 238 | "node": ">=4" 239 | } 240 | }, 241 | "node_modules/balanced-match": { 242 | "version": "1.0.2", 243 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 244 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 245 | "dev": true, 246 | "license": "MIT" 247 | }, 248 | "node_modules/binary-extensions": { 249 | "version": "2.3.0", 250 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 251 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 252 | "dev": true, 253 | "license": "MIT", 254 | "engines": { 255 | "node": ">=8" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/sponsors/sindresorhus" 259 | } 260 | }, 261 | "node_modules/brace-expansion": { 262 | "version": "2.0.1", 263 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 264 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 265 | "dev": true, 266 | "license": "MIT", 267 | "dependencies": { 268 | "balanced-match": "^1.0.0" 269 | } 270 | }, 271 | "node_modules/braces": { 272 | "version": "3.0.3", 273 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 274 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 275 | "dev": true, 276 | "license": "MIT", 277 | "dependencies": { 278 | "fill-range": "^7.1.1" 279 | }, 280 | "engines": { 281 | "node": ">=8" 282 | } 283 | }, 284 | "node_modules/camelcase-css": { 285 | "version": "2.0.1", 286 | "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 287 | "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 288 | "dev": true, 289 | "license": "MIT", 290 | "engines": { 291 | "node": ">= 6" 292 | } 293 | }, 294 | "node_modules/chokidar": { 295 | "version": "3.6.0", 296 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 297 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 298 | "dev": true, 299 | "license": "MIT", 300 | "dependencies": { 301 | "anymatch": "~3.1.2", 302 | "braces": "~3.0.2", 303 | "glob-parent": "~5.1.2", 304 | "is-binary-path": "~2.1.0", 305 | "is-glob": "~4.0.1", 306 | "normalize-path": "~3.0.0", 307 | "readdirp": "~3.6.0" 308 | }, 309 | "engines": { 310 | "node": ">= 8.10.0" 311 | }, 312 | "funding": { 313 | "url": "https://paulmillr.com/funding/" 314 | }, 315 | "optionalDependencies": { 316 | "fsevents": "~2.3.2" 317 | } 318 | }, 319 | "node_modules/chokidar/node_modules/glob-parent": { 320 | "version": "5.1.2", 321 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 322 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 323 | "dev": true, 324 | "license": "ISC", 325 | "dependencies": { 326 | "is-glob": "^4.0.1" 327 | }, 328 | "engines": { 329 | "node": ">= 6" 330 | } 331 | }, 332 | "node_modules/color-convert": { 333 | "version": "2.0.1", 334 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 335 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 336 | "dev": true, 337 | "license": "MIT", 338 | "dependencies": { 339 | "color-name": "~1.1.4" 340 | }, 341 | "engines": { 342 | "node": ">=7.0.0" 343 | } 344 | }, 345 | "node_modules/color-name": { 346 | "version": "1.1.4", 347 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 348 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 349 | "dev": true, 350 | "license": "MIT" 351 | }, 352 | "node_modules/commander": { 353 | "version": "4.1.1", 354 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 355 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 356 | "dev": true, 357 | "license": "MIT", 358 | "engines": { 359 | "node": ">= 6" 360 | } 361 | }, 362 | "node_modules/cross-spawn": { 363 | "version": "7.0.3", 364 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 365 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 366 | "dev": true, 367 | "license": "MIT", 368 | "dependencies": { 369 | "path-key": "^3.1.0", 370 | "shebang-command": "^2.0.0", 371 | "which": "^2.0.1" 372 | }, 373 | "engines": { 374 | "node": ">= 8" 375 | } 376 | }, 377 | "node_modules/cssesc": { 378 | "version": "3.0.0", 379 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 380 | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 381 | "dev": true, 382 | "license": "MIT", 383 | "bin": { 384 | "cssesc": "bin/cssesc" 385 | }, 386 | "engines": { 387 | "node": ">=4" 388 | } 389 | }, 390 | "node_modules/csstype": { 391 | "version": "3.1.3", 392 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 393 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 394 | "dev": true, 395 | "license": "MIT" 396 | }, 397 | "node_modules/didyoumean": { 398 | "version": "1.2.2", 399 | "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 400 | "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", 401 | "dev": true, 402 | "license": "Apache-2.0" 403 | }, 404 | "node_modules/dlv": { 405 | "version": "1.1.3", 406 | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 407 | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 408 | "dev": true, 409 | "license": "MIT" 410 | }, 411 | "node_modules/eastasianwidth": { 412 | "version": "0.2.0", 413 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 414 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 415 | "dev": true, 416 | "license": "MIT" 417 | }, 418 | "node_modules/emoji-regex": { 419 | "version": "9.2.2", 420 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 421 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 422 | "dev": true, 423 | "license": "MIT" 424 | }, 425 | "node_modules/fast-glob": { 426 | "version": "3.3.2", 427 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", 428 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", 429 | "dev": true, 430 | "license": "MIT", 431 | "dependencies": { 432 | "@nodelib/fs.stat": "^2.0.2", 433 | "@nodelib/fs.walk": "^1.2.3", 434 | "glob-parent": "^5.1.2", 435 | "merge2": "^1.3.0", 436 | "micromatch": "^4.0.4" 437 | }, 438 | "engines": { 439 | "node": ">=8.6.0" 440 | } 441 | }, 442 | "node_modules/fast-glob/node_modules/glob-parent": { 443 | "version": "5.1.2", 444 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 445 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 446 | "dev": true, 447 | "license": "ISC", 448 | "dependencies": { 449 | "is-glob": "^4.0.1" 450 | }, 451 | "engines": { 452 | "node": ">= 6" 453 | } 454 | }, 455 | "node_modules/fastq": { 456 | "version": "1.17.1", 457 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", 458 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", 459 | "dev": true, 460 | "license": "ISC", 461 | "dependencies": { 462 | "reusify": "^1.0.4" 463 | } 464 | }, 465 | "node_modules/file-selector": { 466 | "version": "2.1.0", 467 | "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.0.tgz", 468 | "integrity": "sha512-ZuXAqGePcSPz4JuerOY06Dzzq0hrmQ6VGoXVzGyFI1npeOfBgqGIKKpznfYWRkSLJlXutkqVC5WvGZtkFVhu9Q==", 469 | "dev": true, 470 | "license": "MIT", 471 | "dependencies": { 472 | "tslib": "^2.7.0" 473 | }, 474 | "engines": { 475 | "node": ">= 12" 476 | } 477 | }, 478 | "node_modules/fill-range": { 479 | "version": "7.1.1", 480 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 481 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 482 | "dev": true, 483 | "license": "MIT", 484 | "dependencies": { 485 | "to-regex-range": "^5.0.1" 486 | }, 487 | "engines": { 488 | "node": ">=8" 489 | } 490 | }, 491 | "node_modules/foreground-child": { 492 | "version": "3.3.0", 493 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", 494 | "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", 495 | "dev": true, 496 | "license": "ISC", 497 | "dependencies": { 498 | "cross-spawn": "^7.0.0", 499 | "signal-exit": "^4.0.1" 500 | }, 501 | "engines": { 502 | "node": ">=14" 503 | }, 504 | "funding": { 505 | "url": "https://github.com/sponsors/isaacs" 506 | } 507 | }, 508 | "node_modules/fsevents": { 509 | "version": "2.3.3", 510 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 511 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 512 | "dev": true, 513 | "hasInstallScript": true, 514 | "license": "MIT", 515 | "optional": true, 516 | "os": [ 517 | "darwin" 518 | ], 519 | "engines": { 520 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 521 | } 522 | }, 523 | "node_modules/function-bind": { 524 | "version": "1.1.2", 525 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 526 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 527 | "dev": true, 528 | "license": "MIT", 529 | "funding": { 530 | "url": "https://github.com/sponsors/ljharb" 531 | } 532 | }, 533 | "node_modules/glob": { 534 | "version": "10.4.5", 535 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", 536 | "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", 537 | "dev": true, 538 | "license": "ISC", 539 | "dependencies": { 540 | "foreground-child": "^3.1.0", 541 | "jackspeak": "^3.1.2", 542 | "minimatch": "^9.0.4", 543 | "minipass": "^7.1.2", 544 | "package-json-from-dist": "^1.0.0", 545 | "path-scurry": "^1.11.1" 546 | }, 547 | "bin": { 548 | "glob": "dist/esm/bin.mjs" 549 | }, 550 | "funding": { 551 | "url": "https://github.com/sponsors/isaacs" 552 | } 553 | }, 554 | "node_modules/glob-parent": { 555 | "version": "6.0.2", 556 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 557 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 558 | "dev": true, 559 | "license": "ISC", 560 | "dependencies": { 561 | "is-glob": "^4.0.3" 562 | }, 563 | "engines": { 564 | "node": ">=10.13.0" 565 | } 566 | }, 567 | "node_modules/hasown": { 568 | "version": "2.0.2", 569 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 570 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 571 | "dev": true, 572 | "license": "MIT", 573 | "dependencies": { 574 | "function-bind": "^1.1.2" 575 | }, 576 | "engines": { 577 | "node": ">= 0.4" 578 | } 579 | }, 580 | "node_modules/is-binary-path": { 581 | "version": "2.1.0", 582 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 583 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 584 | "dev": true, 585 | "license": "MIT", 586 | "dependencies": { 587 | "binary-extensions": "^2.0.0" 588 | }, 589 | "engines": { 590 | "node": ">=8" 591 | } 592 | }, 593 | "node_modules/is-core-module": { 594 | "version": "2.15.1", 595 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", 596 | "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", 597 | "dev": true, 598 | "license": "MIT", 599 | "dependencies": { 600 | "hasown": "^2.0.2" 601 | }, 602 | "engines": { 603 | "node": ">= 0.4" 604 | }, 605 | "funding": { 606 | "url": "https://github.com/sponsors/ljharb" 607 | } 608 | }, 609 | "node_modules/is-extglob": { 610 | "version": "2.1.1", 611 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 612 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 613 | "dev": true, 614 | "license": "MIT", 615 | "engines": { 616 | "node": ">=0.10.0" 617 | } 618 | }, 619 | "node_modules/is-fullwidth-code-point": { 620 | "version": "3.0.0", 621 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 622 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 623 | "dev": true, 624 | "license": "MIT", 625 | "engines": { 626 | "node": ">=8" 627 | } 628 | }, 629 | "node_modules/is-glob": { 630 | "version": "4.0.3", 631 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 632 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 633 | "dev": true, 634 | "license": "MIT", 635 | "dependencies": { 636 | "is-extglob": "^2.1.1" 637 | }, 638 | "engines": { 639 | "node": ">=0.10.0" 640 | } 641 | }, 642 | "node_modules/is-number": { 643 | "version": "7.0.0", 644 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 645 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 646 | "dev": true, 647 | "license": "MIT", 648 | "engines": { 649 | "node": ">=0.12.0" 650 | } 651 | }, 652 | "node_modules/isexe": { 653 | "version": "2.0.0", 654 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 655 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 656 | "dev": true, 657 | "license": "ISC" 658 | }, 659 | "node_modules/jackspeak": { 660 | "version": "3.4.3", 661 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", 662 | "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", 663 | "dev": true, 664 | "license": "BlueOak-1.0.0", 665 | "dependencies": { 666 | "@isaacs/cliui": "^8.0.2" 667 | }, 668 | "funding": { 669 | "url": "https://github.com/sponsors/isaacs" 670 | }, 671 | "optionalDependencies": { 672 | "@pkgjs/parseargs": "^0.11.0" 673 | } 674 | }, 675 | "node_modules/jiti": { 676 | "version": "1.21.6", 677 | "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", 678 | "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", 679 | "dev": true, 680 | "license": "MIT", 681 | "bin": { 682 | "jiti": "bin/jiti.js" 683 | } 684 | }, 685 | "node_modules/js-tokens": { 686 | "version": "4.0.0", 687 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 688 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 689 | "license": "MIT" 690 | }, 691 | "node_modules/lilconfig": { 692 | "version": "2.1.0", 693 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 694 | "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 695 | "dev": true, 696 | "license": "MIT", 697 | "engines": { 698 | "node": ">=10" 699 | } 700 | }, 701 | "node_modules/lines-and-columns": { 702 | "version": "1.2.4", 703 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 704 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 705 | "dev": true, 706 | "license": "MIT" 707 | }, 708 | "node_modules/loose-envify": { 709 | "version": "1.4.0", 710 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 711 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 712 | "license": "MIT", 713 | "dependencies": { 714 | "js-tokens": "^3.0.0 || ^4.0.0" 715 | }, 716 | "bin": { 717 | "loose-envify": "cli.js" 718 | } 719 | }, 720 | "node_modules/lru-cache": { 721 | "version": "10.4.3", 722 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 723 | "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 724 | "dev": true, 725 | "license": "ISC" 726 | }, 727 | "node_modules/merge2": { 728 | "version": "1.4.1", 729 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 730 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 731 | "dev": true, 732 | "license": "MIT", 733 | "engines": { 734 | "node": ">= 8" 735 | } 736 | }, 737 | "node_modules/micromatch": { 738 | "version": "4.0.8", 739 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 740 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 741 | "dev": true, 742 | "license": "MIT", 743 | "dependencies": { 744 | "braces": "^3.0.3", 745 | "picomatch": "^2.3.1" 746 | }, 747 | "engines": { 748 | "node": ">=8.6" 749 | } 750 | }, 751 | "node_modules/minimatch": { 752 | "version": "9.0.5", 753 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 754 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 755 | "dev": true, 756 | "license": "ISC", 757 | "dependencies": { 758 | "brace-expansion": "^2.0.1" 759 | }, 760 | "engines": { 761 | "node": ">=16 || 14 >=14.17" 762 | }, 763 | "funding": { 764 | "url": "https://github.com/sponsors/isaacs" 765 | } 766 | }, 767 | "node_modules/minipass": { 768 | "version": "7.1.2", 769 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 770 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 771 | "dev": true, 772 | "license": "ISC", 773 | "engines": { 774 | "node": ">=16 || 14 >=14.17" 775 | } 776 | }, 777 | "node_modules/mz": { 778 | "version": "2.7.0", 779 | "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", 780 | "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", 781 | "dev": true, 782 | "license": "MIT", 783 | "dependencies": { 784 | "any-promise": "^1.0.0", 785 | "object-assign": "^4.0.1", 786 | "thenify-all": "^1.0.0" 787 | } 788 | }, 789 | "node_modules/nanoid": { 790 | "version": "3.3.7", 791 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 792 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 793 | "dev": true, 794 | "funding": [ 795 | { 796 | "type": "github", 797 | "url": "https://github.com/sponsors/ai" 798 | } 799 | ], 800 | "license": "MIT", 801 | "bin": { 802 | "nanoid": "bin/nanoid.cjs" 803 | }, 804 | "engines": { 805 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 806 | } 807 | }, 808 | "node_modules/normalize-path": { 809 | "version": "3.0.0", 810 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 811 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 812 | "dev": true, 813 | "license": "MIT", 814 | "engines": { 815 | "node": ">=0.10.0" 816 | } 817 | }, 818 | "node_modules/object-assign": { 819 | "version": "4.1.1", 820 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 821 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 822 | "dev": true, 823 | "license": "MIT", 824 | "engines": { 825 | "node": ">=0.10.0" 826 | } 827 | }, 828 | "node_modules/object-hash": { 829 | "version": "3.0.0", 830 | "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 831 | "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 832 | "dev": true, 833 | "license": "MIT", 834 | "engines": { 835 | "node": ">= 6" 836 | } 837 | }, 838 | "node_modules/package-json-from-dist": { 839 | "version": "1.0.1", 840 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 841 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 842 | "dev": true, 843 | "license": "BlueOak-1.0.0" 844 | }, 845 | "node_modules/path-key": { 846 | "version": "3.1.1", 847 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 848 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 849 | "dev": true, 850 | "license": "MIT", 851 | "engines": { 852 | "node": ">=8" 853 | } 854 | }, 855 | "node_modules/path-parse": { 856 | "version": "1.0.7", 857 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 858 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 859 | "dev": true, 860 | "license": "MIT" 861 | }, 862 | "node_modules/path-scurry": { 863 | "version": "1.11.1", 864 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 865 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 866 | "dev": true, 867 | "license": "BlueOak-1.0.0", 868 | "dependencies": { 869 | "lru-cache": "^10.2.0", 870 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 871 | }, 872 | "engines": { 873 | "node": ">=16 || 14 >=14.18" 874 | }, 875 | "funding": { 876 | "url": "https://github.com/sponsors/isaacs" 877 | } 878 | }, 879 | "node_modules/picocolors": { 880 | "version": "1.1.1", 881 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 882 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 883 | "dev": true, 884 | "license": "ISC" 885 | }, 886 | "node_modules/picomatch": { 887 | "version": "2.3.1", 888 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 889 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 890 | "dev": true, 891 | "license": "MIT", 892 | "engines": { 893 | "node": ">=8.6" 894 | }, 895 | "funding": { 896 | "url": "https://github.com/sponsors/jonschlinkert" 897 | } 898 | }, 899 | "node_modules/pify": { 900 | "version": "2.3.0", 901 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 902 | "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 903 | "dev": true, 904 | "license": "MIT", 905 | "engines": { 906 | "node": ">=0.10.0" 907 | } 908 | }, 909 | "node_modules/pirates": { 910 | "version": "4.0.6", 911 | "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", 912 | "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", 913 | "dev": true, 914 | "license": "MIT", 915 | "engines": { 916 | "node": ">= 6" 917 | } 918 | }, 919 | "node_modules/postcss": { 920 | "version": "8.4.47", 921 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", 922 | "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", 923 | "dev": true, 924 | "funding": [ 925 | { 926 | "type": "opencollective", 927 | "url": "https://opencollective.com/postcss/" 928 | }, 929 | { 930 | "type": "tidelift", 931 | "url": "https://tidelift.com/funding/github/npm/postcss" 932 | }, 933 | { 934 | "type": "github", 935 | "url": "https://github.com/sponsors/ai" 936 | } 937 | ], 938 | "license": "MIT", 939 | "dependencies": { 940 | "nanoid": "^3.3.7", 941 | "picocolors": "^1.1.0", 942 | "source-map-js": "^1.2.1" 943 | }, 944 | "engines": { 945 | "node": "^10 || ^12 || >=14" 946 | } 947 | }, 948 | "node_modules/postcss-import": { 949 | "version": "15.1.0", 950 | "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", 951 | "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", 952 | "dev": true, 953 | "license": "MIT", 954 | "dependencies": { 955 | "postcss-value-parser": "^4.0.0", 956 | "read-cache": "^1.0.0", 957 | "resolve": "^1.1.7" 958 | }, 959 | "engines": { 960 | "node": ">=14.0.0" 961 | }, 962 | "peerDependencies": { 963 | "postcss": "^8.0.0" 964 | } 965 | }, 966 | "node_modules/postcss-js": { 967 | "version": "4.0.1", 968 | "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", 969 | "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", 970 | "dev": true, 971 | "license": "MIT", 972 | "dependencies": { 973 | "camelcase-css": "^2.0.1" 974 | }, 975 | "engines": { 976 | "node": "^12 || ^14 || >= 16" 977 | }, 978 | "funding": { 979 | "type": "opencollective", 980 | "url": "https://opencollective.com/postcss/" 981 | }, 982 | "peerDependencies": { 983 | "postcss": "^8.4.21" 984 | } 985 | }, 986 | "node_modules/postcss-load-config": { 987 | "version": "4.0.2", 988 | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", 989 | "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", 990 | "dev": true, 991 | "funding": [ 992 | { 993 | "type": "opencollective", 994 | "url": "https://opencollective.com/postcss/" 995 | }, 996 | { 997 | "type": "github", 998 | "url": "https://github.com/sponsors/ai" 999 | } 1000 | ], 1001 | "license": "MIT", 1002 | "dependencies": { 1003 | "lilconfig": "^3.0.0", 1004 | "yaml": "^2.3.4" 1005 | }, 1006 | "engines": { 1007 | "node": ">= 14" 1008 | }, 1009 | "peerDependencies": { 1010 | "postcss": ">=8.0.9", 1011 | "ts-node": ">=9.0.0" 1012 | }, 1013 | "peerDependenciesMeta": { 1014 | "postcss": { 1015 | "optional": true 1016 | }, 1017 | "ts-node": { 1018 | "optional": true 1019 | } 1020 | } 1021 | }, 1022 | "node_modules/postcss-load-config/node_modules/lilconfig": { 1023 | "version": "3.1.2", 1024 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", 1025 | "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", 1026 | "dev": true, 1027 | "license": "MIT", 1028 | "engines": { 1029 | "node": ">=14" 1030 | }, 1031 | "funding": { 1032 | "url": "https://github.com/sponsors/antonk52" 1033 | } 1034 | }, 1035 | "node_modules/postcss-nested": { 1036 | "version": "6.2.0", 1037 | "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", 1038 | "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", 1039 | "dev": true, 1040 | "funding": [ 1041 | { 1042 | "type": "opencollective", 1043 | "url": "https://opencollective.com/postcss/" 1044 | }, 1045 | { 1046 | "type": "github", 1047 | "url": "https://github.com/sponsors/ai" 1048 | } 1049 | ], 1050 | "license": "MIT", 1051 | "dependencies": { 1052 | "postcss-selector-parser": "^6.1.1" 1053 | }, 1054 | "engines": { 1055 | "node": ">=12.0" 1056 | }, 1057 | "peerDependencies": { 1058 | "postcss": "^8.2.14" 1059 | } 1060 | }, 1061 | "node_modules/postcss-selector-parser": { 1062 | "version": "6.1.2", 1063 | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", 1064 | "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", 1065 | "dev": true, 1066 | "license": "MIT", 1067 | "dependencies": { 1068 | "cssesc": "^3.0.0", 1069 | "util-deprecate": "^1.0.2" 1070 | }, 1071 | "engines": { 1072 | "node": ">=4" 1073 | } 1074 | }, 1075 | "node_modules/postcss-value-parser": { 1076 | "version": "4.2.0", 1077 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 1078 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 1079 | "dev": true, 1080 | "license": "MIT" 1081 | }, 1082 | "node_modules/prop-types": { 1083 | "version": "15.8.1", 1084 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 1085 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 1086 | "dev": true, 1087 | "license": "MIT", 1088 | "dependencies": { 1089 | "loose-envify": "^1.4.0", 1090 | "object-assign": "^4.1.1", 1091 | "react-is": "^16.13.1" 1092 | } 1093 | }, 1094 | "node_modules/queue-microtask": { 1095 | "version": "1.2.3", 1096 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1097 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1098 | "dev": true, 1099 | "funding": [ 1100 | { 1101 | "type": "github", 1102 | "url": "https://github.com/sponsors/feross" 1103 | }, 1104 | { 1105 | "type": "patreon", 1106 | "url": "https://www.patreon.com/feross" 1107 | }, 1108 | { 1109 | "type": "consulting", 1110 | "url": "https://feross.org/support" 1111 | } 1112 | ], 1113 | "license": "MIT" 1114 | }, 1115 | "node_modules/react": { 1116 | "version": "18.3.1", 1117 | "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 1118 | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 1119 | "license": "MIT", 1120 | "peer": true, 1121 | "dependencies": { 1122 | "loose-envify": "^1.1.0" 1123 | }, 1124 | "engines": { 1125 | "node": ">=0.10.0" 1126 | } 1127 | }, 1128 | "node_modules/react-dom": { 1129 | "version": "18.3.1", 1130 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", 1131 | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", 1132 | "license": "MIT", 1133 | "dependencies": { 1134 | "loose-envify": "^1.1.0", 1135 | "scheduler": "^0.23.2" 1136 | }, 1137 | "peerDependencies": { 1138 | "react": "^18.3.1" 1139 | } 1140 | }, 1141 | "node_modules/react-dropzone": { 1142 | "version": "14.3.2", 1143 | "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.2.tgz", 1144 | "integrity": "sha512-SMfxMjU/lHm6iXHqpRTnYXSbngC08yz51W3NlqS+cBhXpR+orZacLuxO5MJ0gcF+9vV5/K91uniiTCzkmRgriA==", 1145 | "dev": true, 1146 | "license": "MIT", 1147 | "dependencies": { 1148 | "attr-accept": "^2.2.4", 1149 | "file-selector": "^2.1.0", 1150 | "prop-types": "^15.8.1" 1151 | }, 1152 | "engines": { 1153 | "node": ">= 10.13" 1154 | }, 1155 | "peerDependencies": { 1156 | "react": ">= 16.8 || 18.0.0" 1157 | } 1158 | }, 1159 | "node_modules/react-is": { 1160 | "version": "16.13.1", 1161 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 1162 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 1163 | "dev": true, 1164 | "license": "MIT" 1165 | }, 1166 | "node_modules/read-cache": { 1167 | "version": "1.0.0", 1168 | "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", 1169 | "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 1170 | "dev": true, 1171 | "license": "MIT", 1172 | "dependencies": { 1173 | "pify": "^2.3.0" 1174 | } 1175 | }, 1176 | "node_modules/readdirp": { 1177 | "version": "3.6.0", 1178 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1179 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1180 | "dev": true, 1181 | "license": "MIT", 1182 | "dependencies": { 1183 | "picomatch": "^2.2.1" 1184 | }, 1185 | "engines": { 1186 | "node": ">=8.10.0" 1187 | } 1188 | }, 1189 | "node_modules/resolve": { 1190 | "version": "1.22.8", 1191 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 1192 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 1193 | "dev": true, 1194 | "license": "MIT", 1195 | "dependencies": { 1196 | "is-core-module": "^2.13.0", 1197 | "path-parse": "^1.0.7", 1198 | "supports-preserve-symlinks-flag": "^1.0.0" 1199 | }, 1200 | "bin": { 1201 | "resolve": "bin/resolve" 1202 | }, 1203 | "funding": { 1204 | "url": "https://github.com/sponsors/ljharb" 1205 | } 1206 | }, 1207 | "node_modules/reusify": { 1208 | "version": "1.0.4", 1209 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1210 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1211 | "dev": true, 1212 | "license": "MIT", 1213 | "engines": { 1214 | "iojs": ">=1.0.0", 1215 | "node": ">=0.10.0" 1216 | } 1217 | }, 1218 | "node_modules/run-parallel": { 1219 | "version": "1.2.0", 1220 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1221 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1222 | "dev": true, 1223 | "funding": [ 1224 | { 1225 | "type": "github", 1226 | "url": "https://github.com/sponsors/feross" 1227 | }, 1228 | { 1229 | "type": "patreon", 1230 | "url": "https://www.patreon.com/feross" 1231 | }, 1232 | { 1233 | "type": "consulting", 1234 | "url": "https://feross.org/support" 1235 | } 1236 | ], 1237 | "license": "MIT", 1238 | "dependencies": { 1239 | "queue-microtask": "^1.2.2" 1240 | } 1241 | }, 1242 | "node_modules/scheduler": { 1243 | "version": "0.23.2", 1244 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", 1245 | "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", 1246 | "license": "MIT", 1247 | "dependencies": { 1248 | "loose-envify": "^1.1.0" 1249 | } 1250 | }, 1251 | "node_modules/shebang-command": { 1252 | "version": "2.0.0", 1253 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1254 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1255 | "dev": true, 1256 | "license": "MIT", 1257 | "dependencies": { 1258 | "shebang-regex": "^3.0.0" 1259 | }, 1260 | "engines": { 1261 | "node": ">=8" 1262 | } 1263 | }, 1264 | "node_modules/shebang-regex": { 1265 | "version": "3.0.0", 1266 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1267 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1268 | "dev": true, 1269 | "license": "MIT", 1270 | "engines": { 1271 | "node": ">=8" 1272 | } 1273 | }, 1274 | "node_modules/signal-exit": { 1275 | "version": "4.1.0", 1276 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1277 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1278 | "dev": true, 1279 | "license": "ISC", 1280 | "engines": { 1281 | "node": ">=14" 1282 | }, 1283 | "funding": { 1284 | "url": "https://github.com/sponsors/isaacs" 1285 | } 1286 | }, 1287 | "node_modules/source-map-js": { 1288 | "version": "1.2.1", 1289 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1290 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1291 | "dev": true, 1292 | "license": "BSD-3-Clause", 1293 | "engines": { 1294 | "node": ">=0.10.0" 1295 | } 1296 | }, 1297 | "node_modules/string-width": { 1298 | "version": "5.1.2", 1299 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1300 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1301 | "dev": true, 1302 | "license": "MIT", 1303 | "dependencies": { 1304 | "eastasianwidth": "^0.2.0", 1305 | "emoji-regex": "^9.2.2", 1306 | "strip-ansi": "^7.0.1" 1307 | }, 1308 | "engines": { 1309 | "node": ">=12" 1310 | }, 1311 | "funding": { 1312 | "url": "https://github.com/sponsors/sindresorhus" 1313 | } 1314 | }, 1315 | "node_modules/string-width-cjs": { 1316 | "name": "string-width", 1317 | "version": "4.2.3", 1318 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1319 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1320 | "dev": true, 1321 | "license": "MIT", 1322 | "dependencies": { 1323 | "emoji-regex": "^8.0.0", 1324 | "is-fullwidth-code-point": "^3.0.0", 1325 | "strip-ansi": "^6.0.1" 1326 | }, 1327 | "engines": { 1328 | "node": ">=8" 1329 | } 1330 | }, 1331 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 1332 | "version": "5.0.1", 1333 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1334 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1335 | "dev": true, 1336 | "license": "MIT", 1337 | "engines": { 1338 | "node": ">=8" 1339 | } 1340 | }, 1341 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1342 | "version": "8.0.0", 1343 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1344 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1345 | "dev": true, 1346 | "license": "MIT" 1347 | }, 1348 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1349 | "version": "6.0.1", 1350 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1351 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1352 | "dev": true, 1353 | "license": "MIT", 1354 | "dependencies": { 1355 | "ansi-regex": "^5.0.1" 1356 | }, 1357 | "engines": { 1358 | "node": ">=8" 1359 | } 1360 | }, 1361 | "node_modules/strip-ansi": { 1362 | "version": "7.1.0", 1363 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1364 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1365 | "dev": true, 1366 | "license": "MIT", 1367 | "dependencies": { 1368 | "ansi-regex": "^6.0.1" 1369 | }, 1370 | "engines": { 1371 | "node": ">=12" 1372 | }, 1373 | "funding": { 1374 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1375 | } 1376 | }, 1377 | "node_modules/strip-ansi-cjs": { 1378 | "name": "strip-ansi", 1379 | "version": "6.0.1", 1380 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1381 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1382 | "dev": true, 1383 | "license": "MIT", 1384 | "dependencies": { 1385 | "ansi-regex": "^5.0.1" 1386 | }, 1387 | "engines": { 1388 | "node": ">=8" 1389 | } 1390 | }, 1391 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1392 | "version": "5.0.1", 1393 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1394 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1395 | "dev": true, 1396 | "license": "MIT", 1397 | "engines": { 1398 | "node": ">=8" 1399 | } 1400 | }, 1401 | "node_modules/sucrase": { 1402 | "version": "3.35.0", 1403 | "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", 1404 | "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", 1405 | "dev": true, 1406 | "license": "MIT", 1407 | "dependencies": { 1408 | "@jridgewell/gen-mapping": "^0.3.2", 1409 | "commander": "^4.0.0", 1410 | "glob": "^10.3.10", 1411 | "lines-and-columns": "^1.1.6", 1412 | "mz": "^2.7.0", 1413 | "pirates": "^4.0.1", 1414 | "ts-interface-checker": "^0.1.9" 1415 | }, 1416 | "bin": { 1417 | "sucrase": "bin/sucrase", 1418 | "sucrase-node": "bin/sucrase-node" 1419 | }, 1420 | "engines": { 1421 | "node": ">=16 || 14 >=14.17" 1422 | } 1423 | }, 1424 | "node_modules/supports-preserve-symlinks-flag": { 1425 | "version": "1.0.0", 1426 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1427 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1428 | "dev": true, 1429 | "license": "MIT", 1430 | "engines": { 1431 | "node": ">= 0.4" 1432 | }, 1433 | "funding": { 1434 | "url": "https://github.com/sponsors/ljharb" 1435 | } 1436 | }, 1437 | "node_modules/tailwind-merge": { 1438 | "version": "1.14.0", 1439 | "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", 1440 | "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", 1441 | "license": "MIT", 1442 | "funding": { 1443 | "type": "github", 1444 | "url": "https://github.com/sponsors/dcastil" 1445 | } 1446 | }, 1447 | "node_modules/tailwindcss": { 1448 | "version": "3.4.14", 1449 | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", 1450 | "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", 1451 | "dev": true, 1452 | "license": "MIT", 1453 | "dependencies": { 1454 | "@alloc/quick-lru": "^5.2.0", 1455 | "arg": "^5.0.2", 1456 | "chokidar": "^3.5.3", 1457 | "didyoumean": "^1.2.2", 1458 | "dlv": "^1.1.3", 1459 | "fast-glob": "^3.3.0", 1460 | "glob-parent": "^6.0.2", 1461 | "is-glob": "^4.0.3", 1462 | "jiti": "^1.21.0", 1463 | "lilconfig": "^2.1.0", 1464 | "micromatch": "^4.0.5", 1465 | "normalize-path": "^3.0.0", 1466 | "object-hash": "^3.0.0", 1467 | "picocolors": "^1.0.0", 1468 | "postcss": "^8.4.23", 1469 | "postcss-import": "^15.1.0", 1470 | "postcss-js": "^4.0.1", 1471 | "postcss-load-config": "^4.0.1", 1472 | "postcss-nested": "^6.0.1", 1473 | "postcss-selector-parser": "^6.0.11", 1474 | "resolve": "^1.22.2", 1475 | "sucrase": "^3.32.0" 1476 | }, 1477 | "bin": { 1478 | "tailwind": "lib/cli.js", 1479 | "tailwindcss": "lib/cli.js" 1480 | }, 1481 | "engines": { 1482 | "node": ">=14.0.0" 1483 | } 1484 | }, 1485 | "node_modules/thenify": { 1486 | "version": "3.3.1", 1487 | "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", 1488 | "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", 1489 | "dev": true, 1490 | "license": "MIT", 1491 | "dependencies": { 1492 | "any-promise": "^1.0.0" 1493 | } 1494 | }, 1495 | "node_modules/thenify-all": { 1496 | "version": "1.6.0", 1497 | "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", 1498 | "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", 1499 | "dev": true, 1500 | "license": "MIT", 1501 | "dependencies": { 1502 | "thenify": ">= 3.1.0 < 4" 1503 | }, 1504 | "engines": { 1505 | "node": ">=0.8" 1506 | } 1507 | }, 1508 | "node_modules/to-regex-range": { 1509 | "version": "5.0.1", 1510 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1511 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1512 | "dev": true, 1513 | "license": "MIT", 1514 | "dependencies": { 1515 | "is-number": "^7.0.0" 1516 | }, 1517 | "engines": { 1518 | "node": ">=8.0" 1519 | } 1520 | }, 1521 | "node_modules/ts-interface-checker": { 1522 | "version": "0.1.13", 1523 | "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", 1524 | "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", 1525 | "dev": true, 1526 | "license": "Apache-2.0" 1527 | }, 1528 | "node_modules/tslib": { 1529 | "version": "2.8.1", 1530 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 1531 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 1532 | "dev": true, 1533 | "license": "0BSD" 1534 | }, 1535 | "node_modules/typescript": { 1536 | "version": "5.5.4", 1537 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 1538 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 1539 | "license": "Apache-2.0", 1540 | "peer": true, 1541 | "bin": { 1542 | "tsc": "bin/tsc", 1543 | "tsserver": "bin/tsserver" 1544 | }, 1545 | "engines": { 1546 | "node": ">=14.17" 1547 | } 1548 | }, 1549 | "node_modules/util-deprecate": { 1550 | "version": "1.0.2", 1551 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1552 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 1553 | "dev": true, 1554 | "license": "MIT" 1555 | }, 1556 | "node_modules/which": { 1557 | "version": "2.0.2", 1558 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1559 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1560 | "dev": true, 1561 | "license": "ISC", 1562 | "dependencies": { 1563 | "isexe": "^2.0.0" 1564 | }, 1565 | "bin": { 1566 | "node-which": "bin/node-which" 1567 | }, 1568 | "engines": { 1569 | "node": ">= 8" 1570 | } 1571 | }, 1572 | "node_modules/wrap-ansi": { 1573 | "version": "8.1.0", 1574 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1575 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1576 | "dev": true, 1577 | "license": "MIT", 1578 | "dependencies": { 1579 | "ansi-styles": "^6.1.0", 1580 | "string-width": "^5.0.1", 1581 | "strip-ansi": "^7.0.1" 1582 | }, 1583 | "engines": { 1584 | "node": ">=12" 1585 | }, 1586 | "funding": { 1587 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1588 | } 1589 | }, 1590 | "node_modules/wrap-ansi-cjs": { 1591 | "name": "wrap-ansi", 1592 | "version": "7.0.0", 1593 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1594 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1595 | "dev": true, 1596 | "license": "MIT", 1597 | "dependencies": { 1598 | "ansi-styles": "^4.0.0", 1599 | "string-width": "^4.1.0", 1600 | "strip-ansi": "^6.0.0" 1601 | }, 1602 | "engines": { 1603 | "node": ">=10" 1604 | }, 1605 | "funding": { 1606 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1607 | } 1608 | }, 1609 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1610 | "version": "5.0.1", 1611 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1612 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1613 | "dev": true, 1614 | "license": "MIT", 1615 | "engines": { 1616 | "node": ">=8" 1617 | } 1618 | }, 1619 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1620 | "version": "4.3.0", 1621 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1622 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1623 | "dev": true, 1624 | "license": "MIT", 1625 | "dependencies": { 1626 | "color-convert": "^2.0.1" 1627 | }, 1628 | "engines": { 1629 | "node": ">=8" 1630 | }, 1631 | "funding": { 1632 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1633 | } 1634 | }, 1635 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1636 | "version": "8.0.0", 1637 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1638 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1639 | "dev": true, 1640 | "license": "MIT" 1641 | }, 1642 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1643 | "version": "4.2.3", 1644 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1645 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1646 | "dev": true, 1647 | "license": "MIT", 1648 | "dependencies": { 1649 | "emoji-regex": "^8.0.0", 1650 | "is-fullwidth-code-point": "^3.0.0", 1651 | "strip-ansi": "^6.0.1" 1652 | }, 1653 | "engines": { 1654 | "node": ">=8" 1655 | } 1656 | }, 1657 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1658 | "version": "6.0.1", 1659 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1660 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1661 | "dev": true, 1662 | "license": "MIT", 1663 | "dependencies": { 1664 | "ansi-regex": "^5.0.1" 1665 | }, 1666 | "engines": { 1667 | "node": ">=8" 1668 | } 1669 | }, 1670 | "node_modules/yaml": { 1671 | "version": "2.6.0", 1672 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", 1673 | "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", 1674 | "dev": true, 1675 | "license": "ISC", 1676 | "bin": { 1677 | "yaml": "bin.mjs" 1678 | }, 1679 | "engines": { 1680 | "node": ">= 14" 1681 | } 1682 | } 1683 | } 1684 | } 1685 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xixixao/uploadstuff", 3 | "version": "0.0.5", 4 | "author": "xixixao", 5 | "license": "MIT", 6 | "repository": "https://github.com/xixixao/uploadstuff", 7 | "homepage": "https://uploadstuff.dev", 8 | "module": "lib/index.ts", 9 | "types": "lib/index.ts", 10 | "type": "module", 11 | "files": [ 12 | "lib", 13 | "esm", 14 | "cjs", 15 | "README.md" 16 | ], 17 | "exports": { 18 | ".": { 19 | "types": "./lib/index.ts", 20 | "import": "./esm/index.js", 21 | "require": "./cjs/index.js" 22 | }, 23 | "./react": { 24 | "types": "./lib/react.ts", 25 | "import": "./esm/react.js", 26 | "require": "./cjs/react.js" 27 | }, 28 | "./react/styles.css": "./lib/styles.css" 29 | }, 30 | "scripts": { 31 | "publish-docs": "npx vercel build --prod && npx vercel deploy --prebuilt --prod", 32 | "build-css": "tailwindcss -i ./styles.css -o ./lib/styles.css", 33 | "watch-css": "tailwindcss -i ./styles.css -o ./lib/styles.css --watch", 34 | "build": "npm run build-css && rm -rf cjs esm && tsc --outDir esm -t esnext --noEmit false --allowImportingTsExtensions false --composite false --jsx react-jsx && tsc --outDir cjs -t es6 --sourceMap --noEmit false --allowImportingTsExtensions false --composite false -m commonjs --moduleResolution node --jsx react-jsx && echo '{\"type\": \"commonjs\"}' > cjs/package.json" 35 | }, 36 | "dependencies": { 37 | "tailwind-merge": "^1.14.0" 38 | }, 39 | "peerDependencies": { 40 | "react": "^18.2.0", 41 | "react-dropzone": "^14.2.3", 42 | "typescript": "^5.0.0" 43 | }, 44 | "devDependencies": { 45 | "@types/react": "^18.2.21", 46 | "react-dropzone": "^14.2.3", 47 | "tailwindcss": "^3.3.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./lib/**/*.tsx"], 4 | corePlugins: { 5 | preflight: false, 6 | }, 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | experimental: { 12 | optimizeUniversalDefaults: true, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "bundler", 7 | "moduleDetection": "force", 8 | "allowImportingTsExtensions": true, 9 | "noEmit": true, 10 | "composite": true, 11 | "downlevelIteration": true, 12 | "skipLibCheck": true, 13 | "jsx": "preserve", 14 | "allowSyntheticDefaultImports": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowJs": true, 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | 24 | "paths": { 25 | "uploadstuff": ["../.."] 26 | } 27 | }, 28 | "include": ["lib"] 29 | } 30 | --------------------------------------------------------------------------------