Skip to content

Commit

Permalink
@uppy/form: migrate to TS (#4937)
Browse files Browse the repository at this point in the history
Co-authored-by: Antoine du Hamel <antoine@transloadit.com>
Co-authored-by: Mikael Finstad <finstaden@gmail.com>
  • Loading branch information
3 people committed Feb 28, 2024
1 parent 95e4cf3 commit 10c0853
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 144 deletions.
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 {
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",
},
],
}

0 comments on commit 10c0853

Please sign in to comment.