Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/form: migrate to TS #4937

Merged
merged 11 commits into from
Feb 28, 2024
1 change: 1 addition & 0 deletions packages/@uppy/form/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tsconfig.*
144 changes: 0 additions & 144 deletions packages/@uppy/form/src/index.js

This file was deleted.

174 changes: 174 additions & 0 deletions packages/@uppy/form/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import BasePlugin, { type DefinePluginOpts } from '@uppy/core/lib/BasePlugin.js'
import findDOMElement from '@uppy/utils/lib/findDOMElement'
import toArray from '@uppy/utils/lib/toArray'

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore untyped
import getFormData from 'get-form-data'

import type { UIPluginOptions, Uppy, UppyEventMap } from '@uppy/core'
import type { Body, Meta } from '@uppy/utils/lib/UppyFile'
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore We don't want TS to generate types for the package.json
import packageJson from '../package.json'

type Result<M extends Meta, B extends Body> = Parameters<
UppyEventMap<M, B>['complete']
>[0]

export interface FormOptions extends UIPluginOptions {
resultName?: string
getMetaFromForm?: boolean
addResultToForm?: boolean
submitOnSuccess?: boolean
triggerUploadOnSubmit?: boolean
}

const defaultOptions = {
resultName: 'uppyResult',
getMetaFromForm: true,
addResultToForm: true,
submitOnSuccess: false,
triggerUploadOnSubmit: false,
}

type Opts = DefinePluginOpts<FormOptions, keyof typeof defaultOptions>

function assertHTMLFormElement(input: Node | null): HTMLFormElement {
if (input == null || input.nodeName !== 'FORM') {
throw new Error('ASSERTION FAILED: the target is not a <form> element', {
cause: input,
})
}
return input as any
}

export default class Form<M extends Meta, B extends Body> extends BasePlugin<
Opts,
M,
B
> {
static VERSION = packageJson.version

form: HTMLFormElement // TODO: make this private (or at least, mark it as readonly)

constructor(uppy: Uppy<M, B>, opts?: FormOptions) {
super(uppy, { ...defaultOptions, ...opts })
this.type = 'acquirer'
this.id = this.opts.id || 'Form'

this.handleFormSubmit = this.handleFormSubmit.bind(this)
this.handleUploadStart = this.handleUploadStart.bind(this)
this.handleSuccess = this.handleSuccess.bind(this)
this.addResultToForm = this.addResultToForm.bind(this)
this.getMetaFromForm = this.getMetaFromForm.bind(this)
}

handleUploadStart(): void {
if (this.opts.getMetaFromForm) {
this.getMetaFromForm()
}
}

handleSuccess(result: Result<M, B>): void {
if (this.opts.addResultToForm) {
this.addResultToForm(result)
}

if (this.opts.submitOnSuccess) {
this.form.requestSubmit()
}
}

handleFormSubmit(ev: Event): void {
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
if (this.opts.triggerUploadOnSubmit) {
ev.preventDefault()
const elements = toArray((ev.target as HTMLFormElement).elements)
const disabledByUppy: HTMLButtonElement[] = []
elements.forEach((el) => {
const isButton =
el.tagName === 'BUTTON' ||
(el.tagName === 'INPUT' &&
(el as HTMLButtonElement).type === 'submit')
if (isButton && !(el as HTMLButtonElement).disabled) {
;(el as HTMLButtonElement).disabled = true // eslint-disable-line no-param-reassign
disabledByUppy.push(el as HTMLButtonElement)
}
})
this.uppy
.upload()
.then(
() => {
disabledByUppy.forEach((button) => {
button.disabled = false // eslint-disable-line no-param-reassign
})
},
(err) => {
disabledByUppy.forEach((button) => {
button.disabled = false // eslint-disable-line no-param-reassign
})
return Promise.reject(err)
},
)
.catch((err) => {
this.uppy.log(err.stack || err.message || err)
})
}
}

addResultToForm(result: Result<M, B>): void {
this.uppy.log('[Form] Adding result to the original form:')
this.uppy.log(result)

let resultInput: HTMLInputElement | null = this.form.querySelector(
`[name="${this.opts.resultName}"]`,
)
if (resultInput) {
// Append new result to the previous result array.
// If the previous result is empty, or not an array,
// set it to an empty array.
let updatedResult
try {
updatedResult = JSON.parse(resultInput.value)
} catch (err) {
// Nothing, since we check for array below anyway
}

if (!Array.isArray(updatedResult)) {
updatedResult = []
}
updatedResult.push(result)
resultInput.value = JSON.stringify(updatedResult)
return
}

resultInput = document.createElement('input')
resultInput.name = this.opts.resultName
resultInput.type = 'hidden'
resultInput.value = JSON.stringify([result])

this.form.appendChild(resultInput)
}

getMetaFromForm(): void {
const formMeta = getFormData(this.form)
// We want to exclude meta the the Form plugin itself has added
// See https://github.com/transloadit/uppy/issues/1637
delete formMeta[this.opts.resultName]
this.uppy.setMeta(formMeta)
}

install(): void {
this.form = assertHTMLFormElement(findDOMElement(this.opts.target))

this.form.addEventListener('submit', this.handleFormSubmit)
this.uppy.on('upload', this.handleUploadStart)
this.uppy.on('complete', this.handleSuccess)
}

uninstall(): void {
this.form.removeEventListener('submit', this.handleFormSubmit)
this.uppy.off('upload', this.handleUploadStart)
this.uppy.off('complete', this.handleSuccess)
}
}
25 changes: 25 additions & 0 deletions packages/@uppy/form/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"noImplicitAny": false,
"outDir": "./lib",
"paths": {
"@uppy/utils/lib/*": ["../utils/src/*"],
"@uppy/core": ["../core/src/index.js"],
"@uppy/core/lib/*": ["../core/src/*"]
},
"resolveJsonModule": false,
"rootDir": "./src",
"skipLibCheck": true
},
"include": ["./src/**/*.*"],
"exclude": ["./src/**/*.test.ts"],
"references": [
{
"path": "../utils/tsconfig.build.json"
},
{
"path": "../core/tsconfig.build.json"
}
]
}
21 changes: 21 additions & 0 deletions packages/@uppy/form/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.shared",
"compilerOptions": {
"emitDeclarationOnly": false,
"noEmit": true,
"paths": {
"@uppy/utils/lib/*": ["../utils/src/*"],
"@uppy/core": ["../core/src/index.js"],
"@uppy/core/lib/*": ["../core/src/*"],
},
},
"include": ["./package.json", "./src/**/*.*"],
"references": [
{
"path": "../utils/tsconfig.build.json",
},
{
"path": "../core/tsconfig.build.json",
},
],
}