mirror of
https://github.com/transloadit/uppy.git
synced 2026-01-23 02:25:07 +00:00
Merge branch 'main' into dedupe-yarn
* main: [ci] release (#6087) build(deps): bump next from 15.5.2 to 15.5.7 (#6088) Introduce @uppy/image-generator (#6056)
This commit is contained in:
commit
0fc571d9e8
38 changed files with 1895 additions and 276 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Hi, thanks for trying out the bundled version of the Uppy File Uploader. You can
|
||||
use this from a CDN
|
||||
(`<script src="https://releases.transloadit.com/uppy/v5.1.12/uppy.min.js"></script>`)
|
||||
(`<script src="https://releases.transloadit.com/uppy/v5.2.0/uppy.min.js"></script>`)
|
||||
or bundle it with your webapp.
|
||||
|
||||
Note that the recommended way to use Uppy is to install it with yarn/npm and use
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ npm install @uppy/core @uppy/dashboard @uppy/tus
|
|||
```
|
||||
|
||||
Add CSS
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v5.1.12/uppy.min.css),
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v5.2.0/uppy.min.css),
|
||||
either to your HTML page’s `<head>` or include in JS, if your bundler of choice
|
||||
supports it.
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object.
|
|||
```html
|
||||
<!-- 1. Add CSS to `<head>` -->
|
||||
<link
|
||||
href="https://releases.transloadit.com/uppy/v5.1.12/uppy.min.css"
|
||||
href="https://releases.transloadit.com/uppy/v5.2.0/uppy.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object.
|
|||
Uppy,
|
||||
Dashboard,
|
||||
Tus,
|
||||
} from 'https://releases.transloadit.com/uppy/v5.1.12/uppy.min.mjs'
|
||||
} from 'https://releases.transloadit.com/uppy/v5.2.0/uppy.min.mjs'
|
||||
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { target: '#files-drag-drop' })
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"@uppy/transloadit": "workspace:*",
|
||||
"@uppy/tus": "workspace:*",
|
||||
"@uppy/xhr-upload": "workspace:*",
|
||||
"next": "15.5.2",
|
||||
"next": "15.5.7",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
|
|
|
|||
14
packages/@uppy/image-generator/CHANGELOG.md
Normal file
14
packages/@uppy/image-generator/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# @uppy/image-generator
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 5684efa: Introduce @uppy/image-generator to generate images based on a prompt using Transloadit
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5684efa]
|
||||
- Updated dependencies [5684efa]
|
||||
- @uppy/provider-views@5.2.1
|
||||
- @uppy/transloadit@5.4.0
|
||||
21
packages/@uppy/image-generator/LICENSE
Normal file
21
packages/@uppy/image-generator/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Transloadit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
49
packages/@uppy/image-generator/README.md
Normal file
49
packages/@uppy/image-generator/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# @uppy/image-generator
|
||||
|
||||
<img src="https://uppy.io/img/logo.svg" width="120" alt="Uppy logo: a smiling puppy above a pink upwards arrow" align="right">
|
||||
|
||||
[](https://www.npmjs.com/package/@uppy/image-generator)
|
||||

|
||||

|
||||

|
||||
|
||||
**[Read the docs](https://uppy.io/docs/image-generator)** |
|
||||
**[Try it](https://uppy.io/examples/)**
|
||||
|
||||
Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
|
||||
a versatile file encoding service.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core'
|
||||
import ImageGenerator from '@uppy/image-generator'
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(ImageGenerator, {
|
||||
assemblyOptions: async (prompt) => {
|
||||
const res = await fetch(`/assembly-options?prompt=${encodeURIComponent(prompt)}`)
|
||||
return res.json()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install @uppy/image-generator
|
||||
```
|
||||
|
||||
Alternatively, you can also use this plugin in a pre-built bundle from
|
||||
Transloadit’s CDN: Edgly. In that case `Uppy` will attach itself to the global
|
||||
`window.Uppy` object. See the
|
||||
[main Uppy documentation](https://uppy.io/docs/#Installation) for instructions.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation for this plugin can be found on the
|
||||
[Uppy website](https://uppy.io/docs/image-generator).
|
||||
|
||||
## License
|
||||
|
||||
[The MIT License](./LICENSE).
|
||||
60
packages/@uppy/image-generator/package.json
Normal file
60
packages/@uppy/image-generator/package.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "@uppy/image-generator",
|
||||
"description": "AI-powered image generation for Uppy",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"file uploader",
|
||||
"upload",
|
||||
"uppy",
|
||||
"uppy-plugin",
|
||||
"ai",
|
||||
"image generation"
|
||||
],
|
||||
"homepage": "https://uppy.io",
|
||||
"bugs": {
|
||||
"url": "https://github.com/transloadit/uppy/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transloadit/uppy.git"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"lib",
|
||||
"dist",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./css/style.css": "./dist/style.css",
|
||||
"./css/style.min.css": "./dist/style.min.css",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css",
|
||||
"typecheck": "tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uppy/provider-views": "workspace:^",
|
||||
"@uppy/transloadit": "workspace:^",
|
||||
"@uppy/utils": "workspace:^",
|
||||
"preact": "^10.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@uppy/core": "workspace:^",
|
||||
"cssnano": "^7.0.7",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"sass": "^1.89.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "workspace:^"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
381
packages/@uppy/image-generator/src/index.tsx
Normal file
381
packages/@uppy/image-generator/src/index.tsx
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
import type { Body, Meta, MinimalRequiredUppyFile, Uppy } from '@uppy/core'
|
||||
import { UIPlugin, type UIPluginOptions } from '@uppy/core'
|
||||
import { FilterInput, SearchView } from '@uppy/provider-views'
|
||||
import {
|
||||
Assembly,
|
||||
type AssemblyResult,
|
||||
Client,
|
||||
type OptionsWithRestructuredFields,
|
||||
} from '@uppy/transloadit'
|
||||
import { RateLimitedQueue } from '@uppy/utils'
|
||||
import type { h } from 'preact'
|
||||
import locale from './locale.js'
|
||||
|
||||
export interface ImageGeneratorOptions extends UIPluginOptions {
|
||||
// OptionsWithRestructuredFields does not allow string[] for `fields`.
|
||||
// in @uppy/transloadit we do accept that but then immediately use a type assertion to this type
|
||||
// so that's why we just don't allow string[] from the start here
|
||||
assemblyOptions: (prompt: string) => Promise<OptionsWithRestructuredFields>
|
||||
}
|
||||
|
||||
interface PluginState extends Record<string, unknown> {
|
||||
prompt: string
|
||||
results: AssemblyResult[]
|
||||
checkedResultIds: Set<AssemblyResult['id']>
|
||||
loading: boolean
|
||||
loadingMessageIndex: number
|
||||
firstRun: boolean
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
prompt: '',
|
||||
results: [],
|
||||
checkedResultIds: new Set(),
|
||||
loading: false,
|
||||
loadingMessageIndex: 0,
|
||||
firstRun: true,
|
||||
} satisfies PluginState
|
||||
|
||||
const LOADING_MESSAGES = [
|
||||
'generating1',
|
||||
'generating2',
|
||||
'generating3',
|
||||
'generating4',
|
||||
'generating5',
|
||||
] as const
|
||||
|
||||
export default class ImageGenerator<
|
||||
M extends Meta,
|
||||
B extends Body,
|
||||
> extends UIPlugin<ImageGeneratorOptions, M, B, PluginState> {
|
||||
private loadingInterval: ReturnType<typeof setInterval> | null = null
|
||||
private rateLimitedQueue: RateLimitedQueue
|
||||
private client: Client<M, B>
|
||||
private assembly: Assembly | null = null
|
||||
icon: () => h.JSX.Element
|
||||
|
||||
constructor(uppy: Uppy<M, B>, opts: ImageGeneratorOptions) {
|
||||
super(uppy, opts)
|
||||
|
||||
this.id = this.opts.id || 'ImageGenerator'
|
||||
this.title = 'AI image'
|
||||
this.type = 'acquirer'
|
||||
|
||||
this.defaultLocale = locale
|
||||
|
||||
this.rateLimitedQueue = new RateLimitedQueue(10)
|
||||
this.client = new Client({
|
||||
service: 'https://api2.transloadit.com',
|
||||
rateLimitedQueue: this.rateLimitedQueue,
|
||||
errorReporting: true,
|
||||
})
|
||||
|
||||
this.setPluginState(defaultState)
|
||||
|
||||
this.i18nInit()
|
||||
|
||||
this.icon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title>ai-image-generator</title>
|
||||
<defs>
|
||||
<circle id="uppyImageGeneratorCircle" cx="12" cy="12" r="12" />
|
||||
</defs>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<mask id="uppyImageGeneratorMask" fill="#fff">
|
||||
<use xlinkHref="#uppyImageGeneratorCircle" />
|
||||
</mask>
|
||||
<use xlinkHref="#uppyImageGeneratorCircle" fill="#004b9d" />
|
||||
<path
|
||||
fill="#fff"
|
||||
d="m21.98 15.453 2.793.254a.295.295 0 0 1 0 .586l-2.794.254a6 6 0 0 0-5.432 5.432l-.254 2.794a.295.295 0 0 1-.586 0l-.254-2.794a6 6 0 0 0-5.432-5.432l-2.794-.254a.295.295 0 0 1 0-.586l2.794-.254a6 6 0 0 0 5.432-5.432l.254-2.794a.295.295 0 0 1 .586 0l.254 2.794a6 6 0 0 0 5.432 5.432"
|
||||
mask="url(#uppyImageGeneratorMask)"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="m10.74 7.75 1.434.13a.121.121 0 0 1 0 .24l-1.433.13a2.75 2.75 0 0 0-2.49 2.49l-.13 1.434a.121.121 0 0 1-.242 0l-.13-1.433a2.75 2.75 0 0 0-2.49-2.49l-1.433-.13a.121.121 0 0 1 0-.242l1.433-.13a2.75 2.75 0 0 0 2.49-2.49l.13-1.433a.121.121 0 0 1 .242 0l.13 1.433a2.75 2.75 0 0 0 2.49 2.49"
|
||||
mask="url(#uppyImageGeneratorMask)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
install(): void {
|
||||
const { target } = this.opts
|
||||
if (target) {
|
||||
this.mount(target, this)
|
||||
}
|
||||
}
|
||||
|
||||
uninstall(): void {
|
||||
this.clearLoadingInterval()
|
||||
this.closeAssembly(true) // Cancel any in-progress assembly
|
||||
this.unmount()
|
||||
}
|
||||
|
||||
private closeAssembly(cancel = false): void {
|
||||
if (this.assembly) {
|
||||
const { status } = this.assembly
|
||||
this.assembly.close()
|
||||
this.assembly = null
|
||||
|
||||
// Cancel the assembly on the server to stop processing
|
||||
if (cancel && status) {
|
||||
this.client.cancelAssembly(status).catch(() => {
|
||||
// If we can't cancel, there's not much we can do
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private clearLoadingInterval(): void {
|
||||
if (this.loadingInterval) {
|
||||
clearInterval(this.loadingInterval)
|
||||
this.loadingInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
private startLoadingAnimation(): void {
|
||||
this.clearLoadingInterval()
|
||||
this.loadingInterval = setInterval(() => {
|
||||
const { loadingMessageIndex } = this.getPluginState()
|
||||
const nextIndex = (loadingMessageIndex + 1) % LOADING_MESSAGES.length
|
||||
this.setPluginState({ loadingMessageIndex: nextIndex })
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Transloadit assembly to generate AI images.
|
||||
*
|
||||
* Completion scenarios:
|
||||
* - Success: assembly emits 'finished' → resolve() → finally cleans up, keeps results
|
||||
* - Error: assembly emits 'error' → reject() → catch reports error, finally cleans up
|
||||
* - Dashboard close: onDashboardClose sets cancelled=true, resolve() → finally resets state
|
||||
* - Uninstall: closeAssembly(true) called directly, cancels server-side assembly
|
||||
*/
|
||||
generate = async () => {
|
||||
const { loading, prompt } = this.getPluginState()
|
||||
if (loading || prompt.trim() === '') return
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>()
|
||||
let cancelled = false
|
||||
|
||||
const onDashboardClose = () => {
|
||||
cancelled = true
|
||||
resolve()
|
||||
}
|
||||
|
||||
// @ts-expect-error not typed because we do not depend on @uppy/dashboard
|
||||
this.uppy.once('dashboard:close-panel', onDashboardClose)
|
||||
|
||||
try {
|
||||
this.setPluginState({
|
||||
loading: true,
|
||||
results: [],
|
||||
checkedResultIds: new Set(),
|
||||
loadingMessageIndex: 0,
|
||||
})
|
||||
this.startLoadingAnimation()
|
||||
|
||||
const assemblyOptions = await this.opts.assemblyOptions(
|
||||
this.getPluginState().prompt,
|
||||
)
|
||||
|
||||
const assemblyResponse = await this.client.createAssembly({
|
||||
params: assemblyOptions.params,
|
||||
fields: assemblyOptions.fields ?? {},
|
||||
signature: assemblyOptions.signature,
|
||||
expectedFiles: 0,
|
||||
})
|
||||
|
||||
const assembly = new Assembly(assemblyResponse, this.rateLimitedQueue)
|
||||
this.assembly = assembly
|
||||
|
||||
assembly.on('result', (stepName: string, result: AssemblyResult) => {
|
||||
const { results } = this.getPluginState()
|
||||
this.setPluginState({
|
||||
results: [...results, result],
|
||||
firstRun: false,
|
||||
})
|
||||
})
|
||||
|
||||
assembly.on('error', reject)
|
||||
assembly.on('finished', resolve)
|
||||
assembly.connect()
|
||||
|
||||
await promise
|
||||
} catch (error) {
|
||||
this.client.submitError(error as Error).catch(() => {})
|
||||
this.uppy.info('Image could not be generated', 'error')
|
||||
throw error
|
||||
} finally {
|
||||
// @ts-expect-error not typed because we do not depend on @uppy/dashboard
|
||||
this.uppy.off('dashboard:close-panel', onDashboardClose)
|
||||
this.clearLoadingInterval()
|
||||
this.closeAssembly(true)
|
||||
this.setPluginState(cancelled ? defaultState : { loading: false })
|
||||
}
|
||||
}
|
||||
|
||||
private onCheckboxChange = (result: AssemblyResult, event?: Event) => {
|
||||
if (event) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
// Prevent shift-clicking from highlighting file names
|
||||
document.getSelection()?.removeAllRanges()
|
||||
}
|
||||
|
||||
const { checkedResultIds } = this.getPluginState()
|
||||
|
||||
if (checkedResultIds.has(result.id)) {
|
||||
checkedResultIds.delete(result.id)
|
||||
} else {
|
||||
checkedResultIds.add(result.id)
|
||||
}
|
||||
|
||||
this.setPluginState({ checkedResultIds })
|
||||
}
|
||||
|
||||
private cancelSelection = () => {
|
||||
this.setPluginState({ checkedResultIds: new Set() })
|
||||
}
|
||||
|
||||
private donePicking = async () => {
|
||||
const { checkedResultIds, results } = this.getPluginState()
|
||||
const proms: Promise<MinimalRequiredUppyFile<M, B>>[] = results
|
||||
.filter((result) => checkedResultIds.has(result.id))
|
||||
.map(async (result) => {
|
||||
const res = await fetch(result.ssl_url!)
|
||||
const blob = await res.blob()
|
||||
|
||||
return {
|
||||
name: `ai-image-${result.id!}`,
|
||||
type: result.mime ?? undefined,
|
||||
source: 'Transloadit',
|
||||
data: blob,
|
||||
}
|
||||
})
|
||||
const files = await Promise.all(proms)
|
||||
|
||||
this.uppy.addFiles(files)
|
||||
this.setPluginState(defaultState)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
prompt,
|
||||
results,
|
||||
checkedResultIds,
|
||||
loading,
|
||||
loadingMessageIndex,
|
||||
firstRun,
|
||||
} = this.getPluginState()
|
||||
const { i18n } = this.uppy
|
||||
|
||||
const currentLoadingMessage = loading
|
||||
? i18n(LOADING_MESSAGES[loadingMessageIndex])
|
||||
: undefined
|
||||
|
||||
if (firstRun) {
|
||||
return (
|
||||
<SearchView
|
||||
value={prompt}
|
||||
onChange={(prompt) => this.setPluginState({ prompt })}
|
||||
onSubmit={this.generate}
|
||||
inputLabel={i18n('generateImagePlaceholder')}
|
||||
loading={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="uppy-ImageGenerator-generating">
|
||||
{currentLoadingMessage}
|
||||
</span>
|
||||
) : (
|
||||
i18n('generateImage')
|
||||
)}
|
||||
</SearchView>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="uppy-ProviderBrowser uppy-ProviderBrowser-viewType--grid">
|
||||
<FilterInput
|
||||
value={prompt}
|
||||
onChange={(prompt) => this.setPluginState({ prompt })}
|
||||
onSubmit={this.generate}
|
||||
inputLabel={i18n('search')}
|
||||
i18n={i18n}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<div className="uppy-Provider-loading uppy-ImageGenerator-generating--darker">
|
||||
{currentLoadingMessage}
|
||||
</div>
|
||||
) : results.length > 0 ? (
|
||||
<div className="uppy-ProviderBrowser-body">
|
||||
<ul className="uppy-ProviderBrowser-list" tabIndex={-1}>
|
||||
{results.map((result) => (
|
||||
<li
|
||||
key={result.id}
|
||||
className={`uppy-ProviderBrowserItem ${checkedResultIds.has(result.id) ? 'uppy-ProviderBrowserItem--is-checked' : ''}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="uppy-u-reset uppy-ProviderBrowserItem-checkbox uppy-ProviderBrowserItem-checkbox--grid"
|
||||
onChange={(e) => this.onCheckboxChange(result, e)}
|
||||
name="listitem"
|
||||
id={result.id}
|
||||
checked={checkedResultIds.has(result.id)}
|
||||
data-uppy-super-focusable
|
||||
/>
|
||||
<label
|
||||
htmlFor={result.id}
|
||||
aria-label={prompt}
|
||||
className="uppy-u-reset uppy-ProviderBrowserItem-inner"
|
||||
>
|
||||
<img
|
||||
src={result.url!}
|
||||
alt={prompt}
|
||||
referrerPolicy="no-referrer"
|
||||
loading="lazy"
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div className="uppy-Provider-empty">{i18n('noSearchResults')}</div>
|
||||
)}
|
||||
|
||||
{checkedResultIds.size > 0 && (
|
||||
<div className="uppy-ProviderBrowser-footer">
|
||||
<div className="uppy-ProviderBrowser-footer-buttons">
|
||||
<button
|
||||
className="uppy-u-reset uppy-c-btn uppy-c-btn-primary"
|
||||
onClick={this.donePicking}
|
||||
type="button"
|
||||
>
|
||||
{i18n('selectX', {
|
||||
smart_count: checkedResultIds.size,
|
||||
})}
|
||||
</button>
|
||||
<button
|
||||
className="uppy-u-reset uppy-c-btn uppy-c-btn-link"
|
||||
onClick={this.cancelSelection}
|
||||
type="button"
|
||||
>
|
||||
{i18n('cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
12
packages/@uppy/image-generator/src/locale.ts
Normal file
12
packages/@uppy/image-generator/src/locale.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
strings: {
|
||||
generateImage: 'Generate image',
|
||||
generateImagePlaceholder:
|
||||
'A serene sunset over a mountain lake, with pine trees reflecting in the water',
|
||||
generating1: 'AI is thinking...',
|
||||
generating2: 'Crunching pixels...',
|
||||
generating3: 'Summoning images...',
|
||||
generating4: 'AI is working...',
|
||||
generating5: 'Creating magic...',
|
||||
},
|
||||
}
|
||||
29
packages/@uppy/image-generator/src/style.scss
Normal file
29
packages/@uppy/image-generator/src/style.scss
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
@use '@uppy/core/src/_variables.scss' as *;
|
||||
|
||||
.uppy-ImageGenerator-generating {
|
||||
background: linear-gradient(90deg, $gray-50, $gray-300, $gray-50);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: uppy-gradient-slide 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.uppy-ImageGenerator-generating--darker {
|
||||
background: linear-gradient(90deg, $gray-500, $gray-800, $gray-500);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: uppy-gradient-slide 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes uppy-gradient-slide {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
17
packages/@uppy/image-generator/tsconfig.build.json
Normal file
17
packages/@uppy/image-generator/tsconfig.build.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.shared",
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["./src/**/*.*"],
|
||||
"exclude": ["./src/**/*.test.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../core/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
22
packages/@uppy/image-generator/tsconfig.json
Normal file
22
packages/@uppy/image-generator/tsconfig.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.shared",
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["./package.json", "./src/**/*.*"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../core/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../provider-views/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../transloadit/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
packages/@uppy/image-generator/turbo.json
Normal file
8
packages/@uppy/image-generator/turbo.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build", "@uppy/core#build", "@uppy/transloadit#build"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
# @uppy/locales
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5684efa: Introduce @uppy/image-generator to generate images based on a prompt using Transloadit
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/locales",
|
||||
"description": "Uppy language packs",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ en_US.strings = {
|
|||
browseFolders: 'browse folders',
|
||||
cancel: 'Cancel',
|
||||
cancelUpload: 'Cancel upload',
|
||||
chooseFiles: 'Choose files',
|
||||
closeModal: 'Close Modal',
|
||||
companionError: 'Connection with Companion failed',
|
||||
companionUnauthorizeHint:
|
||||
|
|
@ -100,6 +101,14 @@ en_US.strings = {
|
|||
'1': 'Added %{smart_count} files from %{folder}',
|
||||
},
|
||||
folderAlreadyAdded: 'The folder "%{folder}" was already added',
|
||||
generateImage: 'Generate image',
|
||||
generateImagePlaceholder:
|
||||
'A serene sunset over a mountain lake, with pine trees reflecting in the water',
|
||||
generating1: 'AI is thinking...',
|
||||
generating2: 'Crunching pixels...',
|
||||
generating3: 'Summoning images...',
|
||||
generating4: 'AI is working...',
|
||||
generating5: 'Creating magic...',
|
||||
generatingThumbnails: 'Generating thumbnails...',
|
||||
import: 'Import',
|
||||
importFiles: 'Import files from:',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"build": {
|
||||
"dependsOn": [
|
||||
"@uppy/image-editor#build",
|
||||
"@uppy/image-generator#build",
|
||||
"@uppy/box#build",
|
||||
"@uppy/core#build",
|
||||
"@uppy/google-drive-picker#build",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# @uppy/provider-views
|
||||
|
||||
## 5.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5684efa: Refactor internal components
|
||||
|
||||
## 5.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/provider-views",
|
||||
"description": "View library for Uppy remote provider plugins.",
|
||||
"version": "5.2.0",
|
||||
"version": "5.2.1",
|
||||
"license": "MIT",
|
||||
"style": "dist/style.min.css",
|
||||
"type": "module",
|
||||
|
|
|
|||
68
packages/@uppy/provider-views/src/FilterInput.tsx
Normal file
68
packages/@uppy/provider-views/src/FilterInput.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import type { I18n } from '@uppy/utils'
|
||||
import { useSearchForm } from './useSearchForm.js'
|
||||
|
||||
interface FilterInputProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onSubmit: () => void
|
||||
inputLabel: string
|
||||
i18n: I18n
|
||||
}
|
||||
|
||||
/**
|
||||
* FilterInput component for client-side filtering with search icon and clear button.
|
||||
* Supports Enter key submission via form element.
|
||||
*/
|
||||
function FilterInput({
|
||||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
inputLabel,
|
||||
i18n,
|
||||
}: FilterInputProps) {
|
||||
const { formId } = useSearchForm(onSubmit)
|
||||
|
||||
return (
|
||||
<section className="uppy-ProviderBrowser-searchFilter">
|
||||
<input
|
||||
className="uppy-u-reset uppy-ProviderBrowser-searchFilterInput"
|
||||
type="search"
|
||||
aria-label={inputLabel}
|
||||
placeholder={inputLabel}
|
||||
value={value}
|
||||
onInput={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||
form={formId}
|
||||
data-uppy-super-focusable
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className="uppy-c-icon uppy-ProviderBrowser-searchFilterIcon"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M8.638 7.99l3.172 3.172a.492.492 0 1 1-.697.697L7.91 8.656a4.977 4.977 0 0 1-2.983.983C2.206 9.639 0 7.481 0 4.819 0 2.158 2.206 0 4.927 0c2.721 0 4.927 2.158 4.927 4.82a4.74 4.74 0 0 1-1.216 3.17zm-3.71.685c2.176 0 3.94-1.726 3.94-3.856 0-2.129-1.764-3.855-3.94-3.855C2.75.964.984 2.69.984 4.819c0 2.13 1.765 3.856 3.942 3.856z" />
|
||||
</svg>
|
||||
{value && (
|
||||
<button
|
||||
className="uppy-u-reset uppy-ProviderBrowser-searchFilterReset"
|
||||
type="button"
|
||||
aria-label={i18n('resetFilter')}
|
||||
onClick={() => onChange('')}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className="uppy-c-icon"
|
||||
viewBox="0 0 19 19"
|
||||
>
|
||||
<path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilterInput
|
||||
|
|
@ -17,8 +17,8 @@ import debounce from 'lodash/debounce.js'
|
|||
import type { h } from 'preact'
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import Browser from '../Browser.js'
|
||||
import FilterInput from '../FilterInput.js'
|
||||
import FooterActions from '../FooterActions.js'
|
||||
import SearchInput from '../SearchInput.js'
|
||||
import addFiles from '../utils/addFiles.js'
|
||||
import getClickedRange from '../utils/getClickedRange.js'
|
||||
import handleError from '../utils/handleError.js'
|
||||
|
|
@ -678,14 +678,12 @@ export default class ProviderView<M extends Meta, B extends Body> {
|
|||
i18n={i18n}
|
||||
/>
|
||||
{opts.showFilter && (
|
||||
<SearchInput
|
||||
searchString={searchString}
|
||||
setSearchString={(s: string) => this.onSearchInput(s)}
|
||||
submitSearchString={() => {}}
|
||||
<FilterInput
|
||||
value={searchString}
|
||||
onChange={(s: string) => this.onSearchInput(s)}
|
||||
onSubmit={() => {}}
|
||||
inputLabel={i18n('filter')}
|
||||
clearSearchLabel={i18n('resetFilter')}
|
||||
wrapperClassName="uppy-ProviderBrowser-searchFilter"
|
||||
inputClassName="uppy-ProviderBrowser-searchFilterInput"
|
||||
i18n={i18n}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
import { nanoid } from 'nanoid/non-secure'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
|
||||
type Props = {
|
||||
searchString: string
|
||||
setSearchString: (s: string) => void
|
||||
submitSearchString: () => void
|
||||
|
||||
wrapperClassName: string
|
||||
inputClassName: string
|
||||
|
||||
inputLabel: string
|
||||
clearSearchLabel?: string
|
||||
|
||||
showButton?: boolean
|
||||
buttonLabel?: string
|
||||
buttonCSSClassName?: string
|
||||
}
|
||||
|
||||
function SearchInput({
|
||||
searchString,
|
||||
setSearchString,
|
||||
submitSearchString,
|
||||
|
||||
wrapperClassName,
|
||||
inputClassName,
|
||||
|
||||
inputLabel,
|
||||
clearSearchLabel = '',
|
||||
|
||||
showButton = false,
|
||||
buttonLabel = '',
|
||||
buttonCSSClassName = '',
|
||||
}: Props) {
|
||||
const onInput = (e: Event) => {
|
||||
setSearchString((e.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
const submit = useCallback(
|
||||
(ev: Event) => {
|
||||
ev.preventDefault()
|
||||
submitSearchString()
|
||||
},
|
||||
[submitSearchString],
|
||||
)
|
||||
|
||||
// We do this to avoid nested <form>s
|
||||
// (See https://github.com/transloadit/uppy/pull/5050#discussion_r1640392516)
|
||||
const [form] = useState(() => {
|
||||
const formEl = document.createElement('form')
|
||||
formEl.setAttribute('tabindex', '-1')
|
||||
formEl.id = nanoid()
|
||||
return formEl
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
document.body.appendChild(form)
|
||||
form.addEventListener('submit', submit)
|
||||
return () => {
|
||||
form.removeEventListener('submit', submit)
|
||||
document.body.removeChild(form)
|
||||
}
|
||||
}, [form, submit])
|
||||
|
||||
return (
|
||||
<section className={wrapperClassName}>
|
||||
<input
|
||||
className={`uppy-u-reset ${inputClassName}`}
|
||||
type="search"
|
||||
aria-label={inputLabel}
|
||||
placeholder={inputLabel}
|
||||
value={searchString}
|
||||
onInput={onInput}
|
||||
form={form.id}
|
||||
data-uppy-super-focusable
|
||||
/>
|
||||
{!showButton && (
|
||||
// 🔍
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className="uppy-c-icon uppy-ProviderBrowser-searchFilterIcon"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M8.638 7.99l3.172 3.172a.492.492 0 1 1-.697.697L7.91 8.656a4.977 4.977 0 0 1-2.983.983C2.206 9.639 0 7.481 0 4.819 0 2.158 2.206 0 4.927 0c2.721 0 4.927 2.158 4.927 4.82a4.74 4.74 0 0 1-1.216 3.17zm-3.71.685c2.176 0 3.94-1.726 3.94-3.856 0-2.129-1.764-3.855-3.94-3.855C2.75.964.984 2.69.984 4.819c0 2.13 1.765 3.856 3.942 3.856z" />
|
||||
</svg>
|
||||
)}
|
||||
{!showButton && searchString && (
|
||||
// ❌
|
||||
<button
|
||||
className="uppy-u-reset uppy-ProviderBrowser-searchFilterReset"
|
||||
type="button"
|
||||
aria-label={clearSearchLabel}
|
||||
title={clearSearchLabel}
|
||||
onClick={() => setSearchString('')}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
className="uppy-c-icon"
|
||||
viewBox="0 0 19 19"
|
||||
>
|
||||
<path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{showButton && (
|
||||
<button
|
||||
className={`uppy-u-reset uppy-c-btn uppy-c-btn-primary ${buttonCSSClassName}`}
|
||||
type="submit"
|
||||
form={form.id}
|
||||
>
|
||||
{buttonLabel}
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchInput
|
||||
|
|
@ -16,8 +16,9 @@ import classNames from 'classnames'
|
|||
import type { h } from 'preact'
|
||||
import packageJson from '../../package.json' with { type: 'json' }
|
||||
import Browser from '../Browser.js'
|
||||
import FilterInput from '../FilterInput.js'
|
||||
import FooterActions from '../FooterActions.js'
|
||||
import SearchInput from '../SearchInput.js'
|
||||
import SearchView from '../SearchView.js'
|
||||
import addFiles from '../utils/addFiles.js'
|
||||
import getClickedRange from '../utils/getClickedRange.js'
|
||||
import handleError from '../utils/handleError.js'
|
||||
|
|
@ -278,17 +279,14 @@ export default class SearchProviderView<M extends Meta, B extends Body> {
|
|||
|
||||
if (isInputMode) {
|
||||
return (
|
||||
<SearchInput
|
||||
searchString={searchString}
|
||||
setSearchString={this.setSearchString}
|
||||
submitSearchString={this.search}
|
||||
<SearchView
|
||||
value={searchString}
|
||||
onChange={this.setSearchString}
|
||||
onSubmit={this.search}
|
||||
inputLabel={i18n('enterTextToSearch')}
|
||||
buttonLabel={i18n('searchImages')}
|
||||
wrapperClassName="uppy-SearchProvider"
|
||||
inputClassName="uppy-c-textInput uppy-SearchProvider-input"
|
||||
showButton
|
||||
buttonCSSClassName="uppy-SearchProvider-searchButton"
|
||||
/>
|
||||
>
|
||||
{i18n('searchImages')}
|
||||
</SearchView>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -300,14 +298,12 @@ export default class SearchProviderView<M extends Meta, B extends Body> {
|
|||
)}
|
||||
>
|
||||
{opts.showFilter && (
|
||||
<SearchInput
|
||||
searchString={searchString}
|
||||
setSearchString={this.setSearchString}
|
||||
submitSearchString={this.search}
|
||||
<FilterInput
|
||||
value={searchString}
|
||||
onChange={this.setSearchString}
|
||||
onSubmit={this.search}
|
||||
inputLabel={i18n('search')}
|
||||
clearSearchLabel={i18n('resetSearch')}
|
||||
wrapperClassName="uppy-ProviderBrowser-searchFilter"
|
||||
inputClassName="uppy-ProviderBrowser-searchFilterInput"
|
||||
i18n={i18n}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
53
packages/@uppy/provider-views/src/SearchView.tsx
Normal file
53
packages/@uppy/provider-views/src/SearchView.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import type { ComponentChild } from 'preact'
|
||||
import { useSearchForm } from './useSearchForm.js'
|
||||
|
||||
interface SearchViewProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onSubmit: () => void
|
||||
inputLabel: string
|
||||
loading?: boolean
|
||||
children: ComponentChild
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchView component for search with a submit button.
|
||||
* Typically used for initial search views or forms that require explicit submission.
|
||||
* The children prop is rendered as the button content, allowing full control over button text and loading states.
|
||||
*/
|
||||
function SearchView({
|
||||
value,
|
||||
onChange,
|
||||
onSubmit,
|
||||
inputLabel,
|
||||
loading = false,
|
||||
children,
|
||||
}: SearchViewProps) {
|
||||
const { formId } = useSearchForm(onSubmit)
|
||||
|
||||
return (
|
||||
<section className="uppy-SearchProvider">
|
||||
<input
|
||||
className="uppy-u-reset uppy-c-textInput uppy-SearchProvider-input"
|
||||
type="search"
|
||||
aria-label={inputLabel}
|
||||
placeholder={inputLabel}
|
||||
value={value}
|
||||
onInput={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||
form={formId}
|
||||
disabled={loading}
|
||||
data-uppy-super-focusable
|
||||
/>
|
||||
<button
|
||||
disabled={loading}
|
||||
className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-SearchProvider-searchButton"
|
||||
type="submit"
|
||||
form={formId}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchView
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as FilterInput } from './FilterInput.js'
|
||||
export { default as GooglePickerView } from './GooglePicker/GooglePickerView.js'
|
||||
export type {
|
||||
MediaItem,
|
||||
|
|
@ -24,5 +25,6 @@ export {
|
|||
default as ProviderViews,
|
||||
defaultPickerIcon,
|
||||
} from './ProviderView/index.js'
|
||||
export { default as SearchInput } from './SearchInput.js'
|
||||
export { default as SearchProviderViews } from './SearchProviderView/index.js'
|
||||
export { default as SearchView } from './SearchView.js'
|
||||
export { useSearchForm } from './useSearchForm.js'
|
||||
|
|
|
|||
42
packages/@uppy/provider-views/src/useSearchForm.ts
Normal file
42
packages/@uppy/provider-views/src/useSearchForm.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { nanoid } from 'nanoid/non-secure'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
|
||||
/**
|
||||
* Hook to create a form element outside the component tree to avoid nested forms.
|
||||
* Returns a formId that can be used with the `form` attribute on inputs and buttons.
|
||||
*
|
||||
* This allows form submission (Enter key) to work properly even when the component
|
||||
* is rendered inside another form element.
|
||||
*
|
||||
* @param onSubmit - Callback to execute when the form is submitted
|
||||
* @returns Object containing the formId to use with form attribute
|
||||
*/
|
||||
export function useSearchForm(onSubmit: () => void): { formId: string } {
|
||||
const submit = useCallback(
|
||||
(ev: Event) => {
|
||||
ev.preventDefault()
|
||||
onSubmit()
|
||||
},
|
||||
[onSubmit],
|
||||
)
|
||||
|
||||
// We create a form element and append it to document.body to avoid nested <form>s
|
||||
// (See https://github.com/transloadit/uppy/pull/5050#discussion_r1640392516)
|
||||
const [form] = useState(() => {
|
||||
const formEl = document.createElement('form')
|
||||
formEl.setAttribute('tabindex', '-1')
|
||||
formEl.id = nanoid()
|
||||
return formEl
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
document.body.appendChild(form)
|
||||
form.addEventListener('submit', submit)
|
||||
return () => {
|
||||
form.removeEventListener('submit', submit)
|
||||
document.body.removeChild(form)
|
||||
}
|
||||
}, [form, submit])
|
||||
|
||||
return { formId: form.id }
|
||||
}
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
# @uppy/transloadit
|
||||
|
||||
## 5.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5684efa: Export Assembly, AssemblyError, Client
|
||||
|
||||
## 5.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/transloadit",
|
||||
"description": "The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, and more",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
|
|
|
|||
|
|
@ -819,7 +819,9 @@ export default class Transloadit<
|
|||
: this.opts.assemblyOptions
|
||||
) as OptionsWithRestructuredFields
|
||||
|
||||
assemblyOptions.fields ??= {}
|
||||
assemblyOptions.fields = {
|
||||
...(assemblyOptions.fields ?? {}),
|
||||
}
|
||||
validateParams(assemblyOptions.params)
|
||||
|
||||
try {
|
||||
|
|
@ -1032,3 +1034,7 @@ export default class Transloadit<
|
|||
}
|
||||
|
||||
export { COMPANION_URL, COMPANION_ALLOWED_HOSTS }
|
||||
|
||||
// Low-level classes for advanced usage (e.g., creating assemblies without file uploads)
|
||||
export { default as Assembly } from './Assembly.js'
|
||||
export { AssemblyError, default as Client } from './Client.js'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# @uppy/webdav
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5684efa: Refactor internal components
|
||||
- Updated dependencies [5684efa]
|
||||
- @uppy/provider-views@5.2.1
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@uppy/webdav",
|
||||
"description": "Import files from WebDAV into Uppy.",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"license": "MIT",
|
||||
"types": "types/index.d.ts",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type {
|
|||
} from '@uppy/core'
|
||||
|
||||
import { UIPlugin } from '@uppy/core'
|
||||
import { ProviderViews, SearchInput } from '@uppy/provider-views'
|
||||
import { ProviderViews, SearchView } from '@uppy/provider-views'
|
||||
import type { I18n, LocaleStrings } from '@uppy/utils'
|
||||
// biome-ignore lint/style/useImportType: h is not a type
|
||||
import { type ComponentChild, h } from 'preact'
|
||||
|
|
@ -61,17 +61,14 @@ const AuthForm = ({
|
|||
}, [onAuth, webdavUrl])
|
||||
|
||||
return (
|
||||
<SearchInput
|
||||
searchString={webdavUrl}
|
||||
setSearchString={setWebdavUrl}
|
||||
submitSearchString={onSubmit}
|
||||
<SearchView
|
||||
value={webdavUrl}
|
||||
onChange={setWebdavUrl}
|
||||
onSubmit={onSubmit}
|
||||
inputLabel={i18n('pluginWebdavInputLabel')}
|
||||
buttonLabel={i18n('authenticate')}
|
||||
wrapperClassName="uppy-SearchProvider"
|
||||
inputClassName="uppy-c-textInput uppy-SearchProvider-input"
|
||||
showButton
|
||||
buttonCSSClassName="uppy-SearchProvider-searchButton"
|
||||
/>
|
||||
>
|
||||
{i18n('authenticate')}
|
||||
</SearchView>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,22 @@
|
|||
# uppy
|
||||
|
||||
## 5.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5684efa: Introduce @uppy/image-generator to generate images based on a prompt using Transloadit
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5684efa]
|
||||
- Updated dependencies [5684efa]
|
||||
- Updated dependencies [5684efa]
|
||||
- @uppy/provider-views@5.2.1
|
||||
- @uppy/webdav@1.1.1
|
||||
- @uppy/transloadit@5.4.0
|
||||
- @uppy/image-generator@1.0.0
|
||||
- @uppy/locales@5.1.0
|
||||
|
||||
## 5.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "uppy",
|
||||
"description": "Extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:",
|
||||
"version": "5.1.12",
|
||||
"version": "5.2.0",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
|
|
@ -54,6 +54,7 @@
|
|||
"@uppy/google-drive-picker": "workspace:*",
|
||||
"@uppy/google-photos-picker": "workspace:*",
|
||||
"@uppy/image-editor": "workspace:*",
|
||||
"@uppy/image-generator": "workspace:*",
|
||||
"@uppy/instagram": "workspace:*",
|
||||
"@uppy/locales": "workspace:*",
|
||||
"@uppy/onedrive": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@
|
|||
@use '@uppy/screen-capture/src/style.scss' as screen-capture;
|
||||
@use '@uppy/status-bar/src/style.scss' as status-bar;
|
||||
@use '@uppy/image-editor/src/style.scss' as image-editor;
|
||||
@use '@uppy/image-generator/src/style.scss' as image-generator;
|
||||
@use '@uppy/drop-target/src/style.scss' as drop-target;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import GoogleDrive from '@uppy/google-drive'
|
|||
import GoogleDrivePicker from '@uppy/google-drive-picker'
|
||||
import GooglePhotosPicker from '@uppy/google-photos-picker'
|
||||
import ImageEditor from '@uppy/image-editor'
|
||||
import ImageGenerator from '@uppy/image-generator'
|
||||
import english from '@uppy/locales/lib/en_US.js'
|
||||
import RemoteSources from '@uppy/remote-sources'
|
||||
import ScreenCapture from '@uppy/screen-capture'
|
||||
|
|
@ -174,6 +175,25 @@ export default () => {
|
|||
.use(ScreenCapture, { target: Dashboard })
|
||||
.use(Form, { target: '#upload-form' })
|
||||
.use(ImageEditor, { target: Dashboard })
|
||||
.use(ImageGenerator, {
|
||||
target: Dashboard,
|
||||
assemblyOptions: async (prompt) =>
|
||||
// never create a signature on the client in production!
|
||||
// it will expose the secret on the client
|
||||
generateSignatureIfSecret(TRANSLOADIT_SECRET, {
|
||||
auth: { key: TRANSLOADIT_KEY },
|
||||
steps: {
|
||||
generated_image: {
|
||||
robot: '/image/generate',
|
||||
result: true,
|
||||
aspect_ratio: '2:3',
|
||||
model: 'flux-1.1-pro-ultra',
|
||||
prompt,
|
||||
num_outputs: 2,
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
.use(DropTarget, {
|
||||
target: document.body,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@
|
|||
],
|
||||
"outputs": ["lib/**", "dist/**"]
|
||||
},
|
||||
"uppy#build:css": {
|
||||
"outputLogs": "new-only",
|
||||
"dependsOn": ["^build:css"],
|
||||
"inputs": ["src/**/*.scss"],
|
||||
"outputs": ["lib/**/*.css", "dist/**/*.css"]
|
||||
},
|
||||
"build:css": {
|
||||
"outputLogs": "new-only",
|
||||
"inputs": ["src/**/*.scss"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue