Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/eighty-words-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@uppy/screen-capture": minor
"@uppy/dashboard": minor
"@uppy/core": minor
---

Narrow a number of `any` types to real types. Some affect the public interface.

This also fixes some bugs the new types revealed:

- The "copy link" helper now actually applies its styles to the temporary textarea it creates. Previously the style object was stringified to `"[object Object]"`, leaving the textarea unstyled and able to scroll the page when selected.
- Cancelling the file card now emits `dashboard:file-edit-complete` with the file being edited, instead of `undefined`.
- The "missing required meta fields" message now passes the file to a `metaFields` callback, which previously received `undefined`, and no longer throws when a field listed in the `requiredMetaFields` restriction has no matching entry in `metaFields`.
2 changes: 1 addition & 1 deletion packages/@uppy/core/src/UIPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class UIPlugin<

isTargetDOMEl!: boolean

el!: HTMLElement | null
el: HTMLElement | null = null

parent: unknown

Expand Down
14 changes: 8 additions & 6 deletions packages/@uppy/dashboard/src/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { UIPlugin } from '@uppy/core'
import { defaultPickerIcon } from '@uppy/core/provider-views'
import type { LocaleStrings } from '@uppy/core/utils'
import { findAllDOMElements, getDroppedFiles, toArray } from '@uppy/core/utils'
import type { ComponentChild, h, VNode } from '@uppy/core/utils/preact'
import type { ComponentChild, VNode } from '@uppy/core/utils/preact'
import ThumbnailGenerator from '@uppy/thumbnail-generator'
import { nanoid } from 'nanoid/non-secure'
import type { TargetedEvent } from 'preact'
import packageJson from '../package.json' with { type: 'json' }
import DashboardUI from './components/Dashboard.js'
import locale from './locale.js'
Expand Down Expand Up @@ -78,11 +79,12 @@ type PreactRender = (
...children: any[]
) => VNode<any>

interface MetaField {
export interface MetaField {
id: string
name: string
placeholder?: string
render?: (field: FieldRenderOptions, h: PreactRender) => VNode<any>
type?: string
}

interface Target {
Expand Down Expand Up @@ -747,7 +749,7 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
trapFocus.forModal(
event,
this.getPluginState().activeOverlayType,
this.el,
this.el!,
)
}

Expand All @@ -773,7 +775,7 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
}

private handleInputChange = (
event: h.JSX.TargetedEvent<HTMLInputElement, Event>,
event: TargetedEvent<HTMLInputElement, Event>,
) => {
event.preventDefault()
const files = toArray(event.currentTarget.files || [])
Expand Down Expand Up @@ -902,7 +904,7 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
trapFocus.forInline(
event,
this.getPluginState().activeOverlayType,
this.el,
this.el!,
)
}

Expand Down Expand Up @@ -1076,7 +1078,7 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
// try to press space multiple times. Focus will jump to Uppy.
(isFocusNowhere && this.ifFocusedOnUppyRecently))
) {
this.superFocus(this.el, this.getPluginState().activeOverlayType)
this.superFocus(this.el!, this.getPluginState().activeOverlayType)
} else {
this.superFocus.cancel()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@uppy/dashboard/src/components/AddFiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@uppy/core/utils/preact'
import type { DashboardState, TargetWithRender } from '../Dashboard.js'

interface AddFilesProps {
export interface AddFilesProps {
i18n: I18n
i18nArray: Translator['translateArray']
acquirers: TargetWithRender[]
Expand Down
11 changes: 8 additions & 3 deletions packages/@uppy/dashboard/src/components/AddFilesPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import classNames from 'classnames'
import type { ComponentChildren } from 'preact'
import type { AddFilesProps } from './AddFiles.js'
import AddFiles from './AddFiles.js'

type $TSFixMe = any
interface AddFilesPanelProps extends AddFilesProps {
className?: string | undefined
showAddFilesPanel: boolean
toggleAddFilesPanel: (enabled: boolean) => void
}

const AddFilesPanel = (props: $TSFixMe): $TSFixMe => {
const AddFilesPanel = (props: AddFilesPanelProps): ComponentChildren => {
return (
<div
className={classNames('uppy-Dashboard-AddFilesPanel', props.className)}
Expand All @@ -26,7 +32,6 @@ const AddFilesPanel = (props: $TSFixMe): $TSFixMe => {
{props.i18n('back')}
</button>
</div>
{}
<AddFiles {...props} />
</div>
)
Expand Down
18 changes: 12 additions & 6 deletions packages/@uppy/dashboard/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ type DashboardUIProps<M extends Meta, B extends Body> = {
parentElement: HTMLElement | null
allowedFileTypes: string[] | null
maxNumberOfFiles: number | null
requiredMetaFields: any
requiredMetaFields: string[]
showSelectedFiles: boolean
showNativePhotoCameraButton: boolean
showNativeVideoCameraButton: boolean
Expand All @@ -131,9 +131,9 @@ type DashboardUIProps<M extends Meta, B extends Body> = {
export default function Dashboard<M extends Meta, B extends Body>(
props: DashboardUIProps<M, B>,
) {
const { activePickerPanel, fileCardFor } = props
const isNoFiles = props.totalFileCount === 0
const isSingleFile = props.totalFileCount === 1
const isSizeMD = props.containerWidth > WIDTH_MD
const isSizeHeightMD = props.containerHeight > HEIGHT_MD

const dashboardClassName = classNames({
Expand Down Expand Up @@ -320,17 +320,23 @@ export default function Dashboard<M extends Meta, B extends Body>(

<Slide>
{props.showAddFilesPanel ? (
<AddFilesPanel key="AddFiles" {...props} isSizeMD={isSizeMD} />
<AddFilesPanel key="AddFiles" {...props} />
) : null}
</Slide>

<Slide>
{props.fileCardFor ? <FileCard key="FileCard" {...props} /> : null}
{fileCardFor ? (
<FileCard key="FileCard" {...props} fileCardFor={fileCardFor} />
) : null}
</Slide>

<Slide>
{props.activePickerPanel ? (
<PickerPanelContent key="Picker" {...props} />
{activePickerPanel ? (
<PickerPanelContent
key="Picker"
{...props}
activePickerPanel={activePickerPanel}
/>
) : null}
</Slide>

Expand Down
51 changes: 46 additions & 5 deletions packages/@uppy/dashboard/src/components/EditorPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import type Uppy from '@uppy/core'
import type { Body, State, UIPlugin } from '@uppy/core'
import type {
I18n,
Meta,
Translator,
UppyFile,
UppyFileId,
} from '@uppy/core/utils'
import classNames from 'classnames'
import type { ComponentChildren, MouseEventHandler } from 'preact'
import type { TargetWithRender } from '../Dashboard.js'

type $TSFixMe = any
declare module '@uppy/core' {
export interface UppyEventMap<M extends Meta, B extends Body> {
'file-editor:cancel': (file: UppyFile<M, B>) => void
}
}

type EditorPanelProps<M extends Meta, B extends Body> = {
className?: string | undefined
closeFileEditor: () => void
editors: TargetWithRender[]
fileCardFor: UppyFileId | null
files: State<M, B>['files']
i18n: I18n
i18nArray: Translator['translateArray']
saveFileEditor: MouseEventHandler<HTMLButtonElement>
state: State<M, B>
uppy: Uppy<M, B>
}

function EditorPanel<M extends Meta, B extends Body>(
props: EditorPanelProps<M, B>,
): ComponentChildren {
const file = props.fileCardFor ? props.files[props.fileCardFor] : undefined

function EditorPanel(props: $TSFixMe) {
const file = props.files[props.fileCardFor]
if (!file) {
return null
}

const handleCancel = () => {
props.uppy.emit('file-editor:cancel', file)
Expand Down Expand Up @@ -47,8 +81,15 @@ function EditorPanel(props: $TSFixMe) {
</button>
</div>
<div className="uppy-DashboardContent-panelBody">
{props.editors.map((target: $TSFixMe) => {
return props.uppy.getPlugin(target.id).render(props.state)
{props.editors.map((target) => {
return (
props.uppy.getPlugin(target.id) as UIPlugin<
// biome-ignore lint/complexity/noBannedTypes: {} means anything except null or undefined.
{},
M,
B
>
).render(props.state)
})}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { h } from '@uppy/core/utils/preact'
import { type ComponentChildren, h } from '@uppy/core/utils/preact'
import type { MetaField } from '../../Dashboard.js'

type $TSFixMe = any
interface RenderMetaFieldsProps {
computedMetaFields: MetaField[]
requiredMetaFields: string[]
updateMeta: (newVal: string, fieldId: string) => void
form: HTMLFormElement
formState: Record<string, string>
}

export default function RenderMetaFields(props: $TSFixMe) {
export default function RenderMetaFields(
props: RenderMetaFieldsProps,
): ComponentChildren {
const {
computedMetaFields,
requiredMetaFields,
Expand All @@ -15,7 +24,7 @@ export default function RenderMetaFields(props: $TSFixMe) {
text: 'uppy-u-reset uppy-c-textInput uppy-Dashboard-FileCard-input',
}

return computedMetaFields.map((field: $TSFixMe) => {
return computedMetaFields.map((field) => {
const id = `uppy-Dashboard-FileCard-input-${field.id}`
const required = requiredMetaFields.includes(field.id)
return (
Expand All @@ -27,7 +36,7 @@ export default function RenderMetaFields(props: $TSFixMe) {
field.render(
{
value: formState[field.id],
onChange: (newVal: $TSFixMe) => updateMeta(newVal, field.id),
onChange: (newVal) => updateMeta(newVal, field.id),
fieldCSSClasses,
required,
form: form.id,
Expand Down
36 changes: 28 additions & 8 deletions packages/@uppy/dashboard/src/components/FileCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Body, Meta, State } from '@uppy/core'
import type { I18n, Translator, UppyFile } from '@uppy/core/utils'
import {
useCallback,
useEffect,
Expand All @@ -6,14 +8,30 @@ import {
} from '@uppy/core/utils/preact/hooks'
import classNames from 'classnames'
import { nanoid } from 'nanoid/non-secure'
import type { ComponentChildren, TargetedMouseEvent } from 'preact'
import type { DashboardState } from '../../Dashboard.js'
import getFileTypeIcon from '../../utils/getFileTypeIcon.js'
import ignoreEvent from '../../utils/ignoreEvent.js'
import FilePreview from '../FilePreview.js'
import RenderMetaFields from './RenderMetaFields.js'

type $TSFixMe = any
interface FileCardProps<M extends Meta, B extends Body> {
canEditFile: (file: UppyFile<M, B>) => boolean
className?: string | undefined
fileCardFor: string
files: State<M, B>['files']
i18n: I18n
i18nArray: Translator['translateArray']
metaFields: DashboardState<M, B>['metaFields']
openFileEditor: (file: UppyFile<M, B>) => void
requiredMetaFields: string[]
saveFileCard: (meta: M, fileID: string) => void
toggleFileCard: (show: boolean, fileID: string) => void
}

export default function FileCard(props: $TSFixMe) {
export default function FileCard<M extends Meta, B extends Body>(
props: FileCardProps<M, B>,
): ComponentChildren {
const {
files,
fileCardFor,
Expand All @@ -38,30 +56,31 @@ export default function FileCard(props: $TSFixMe) {
const computedMetaFields = getMetaFields() ?? []
const showEditButton = canEditFile(file)

const storedMetaData: Record<string, string> = {}
computedMetaFields.forEach((field: $TSFixMe) => {
const storedMetaData = {} as M
computedMetaFields.forEach((field) => {
// @ts-expect-error TODO fix me
storedMetaData[field.id] = file.meta[field.id] ?? ''
})

const [formState, setFormState] = useState(storedMetaData)

const handleSave = useCallback(
(ev: $TSFixMe) => {
(ev: SubmitEvent | TargetedMouseEvent<HTMLElement>) => {
ev.preventDefault()
saveFileCard(formState, fileCardFor)
},
[saveFileCard, formState, fileCardFor],
)

const updateMeta = (newVal: $TSFixMe, name: $TSFixMe) => {
const updateMeta = (newVal: string, name: string) => {
setFormState({
...formState,
[name]: newVal,
})
}

const handleCancel = () => {
toggleFileCard(false)
toggleFileCard(false, fileCardFor)
}

const [form] = useState(() => {
Expand Down Expand Up @@ -152,7 +171,7 @@ export default function FileCard(props: $TSFixMe) {
<button
type="button"
className="uppy-u-reset uppy-c-btn uppy-Dashboard-FileCard-edit"
onClick={(event: $TSFixMe) => {
onClick={(event) => {
// When opening the image editor we want to save any meta fields changes.
// Otherwise it's confusing for the user to click save in the editor,
// but the changes here are discarded. This bypasses validation,
Expand All @@ -175,6 +194,7 @@ export default function FileCard(props: $TSFixMe) {
requiredMetaFields={requiredMetaFields}
updateMeta={updateMeta}
form={form}
// @ts-expect-error TODO fix me
formState={formState}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type Uppy from '@uppy/core'
import type { Body, Meta, UppyFile } from '@uppy/core'
import type { I18n } from '@uppy/core/utils'
import type { MouseEventHandler } from 'preact'
import type { DashboardState } from '../../../Dashboard.js'
import copyToClipboard from '../../../utils/copyToClipboard.js'

type $TSFixMe = any

function EditButton<M extends Meta, B extends Body>({
file,
uploadInProgressOrComplete,
Expand Down Expand Up @@ -103,15 +102,16 @@ function CopyLinkButton<M extends Meta, B extends Body>({
uppy: Uppy<M, B>
i18n: I18n
}) {
const copyLinkToClipboard = (event: $TSFixMe) => {
copyToClipboard(file.uploadURL, i18n('copyLinkToClipboardFallback'))
const copyLinkToClipboard: MouseEventHandler<HTMLButtonElement> = (event) => {
const { currentTarget } = event
copyToClipboard(file.uploadURL!, i18n('copyLinkToClipboardFallback'))
.then(() => {
uppy.log('Link copied to clipboard.')
uppy.info(i18n('copyLinkToClipboardSuccess'), 'info', 3000)
})
.catch(uppy.log)
// avoid losing focus
.then(() => event.target.focus({ preventScroll: true }))
.then(() => currentTarget.focus({ preventScroll: true }))
}

return (
Expand Down
Loading