Skip to content

Commit 361ee51

Browse files
authored
Merge pull request Sofie-Automation#1418 from SuperFlyTV/chore/upload-button-refactoring
chore: refactor UploadButton to avoid duplication
2 parents 61e12a0 + f9b0f52 commit 361ee51

File tree

9 files changed

+606
-621
lines changed

9 files changed

+606
-621
lines changed

packages/webui/src/client/lib/Components/Base64ImageInput.tsx

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useState } from 'react'
1+
import { useCallback } from 'react'
22
import { UploadButton } from '../uploadButton'
33
import { faUpload } from '@fortawesome/free-solid-svg-icons'
44
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@@ -19,37 +19,27 @@ export function Base64ImageInputControl({
1919
}: Readonly<IBase64ImageInputControlProps>): JSX.Element {
2020
const { t } = useTranslation()
2121

22-
const [uploadFileKey, setUploadFileKey] = useState(() => Date.now())
23-
2422
const handleSelectFile = useCallback(
25-
(event: React.ChangeEvent<HTMLInputElement>) => {
26-
// Clear the field
27-
setUploadFileKey(Date.now())
28-
29-
const file = event.target.files?.[0]
30-
if (!file) return
31-
32-
const reader = new FileReader()
33-
reader.onload = (readEvent) => {
34-
// On file upload
23+
(fileContent: string) => {
24+
if (typeof fileContent !== 'string' || !fileContent) return
3525

36-
const uploadResult = readEvent.target?.result
37-
if (typeof uploadResult !== 'string' || !uploadResult) return
38-
39-
handleUpdate(uploadResult.toString())
40-
}
41-
reader.readAsDataURL(file)
26+
handleUpdate(fileContent)
4227
},
4328
[handleUpdate]
4429
)
4530

31+
const handleUploadError = useCallback((error: Error) => {
32+
// Handle upload error
33+
console.error('Error uploading file:', error)
34+
}, [])
35+
4636
return (
4737
<div className={classNames}>
4838
<UploadButton
4939
className="btn btn-primary"
5040
accept="image/*"
51-
onChange={handleSelectFile}
52-
key={uploadFileKey}
41+
onUploadContents={handleSelectFile}
42+
onUploadError={handleUploadError}
5343
disabled={disabled}
5444
>
5545
<FontAwesomeIcon icon={faUpload} />

packages/webui/src/client/lib/forms/SchemaFormTable/ObjectTable.tsx

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { faDownload, faPlus, faUpload } from '@fortawesome/free-solid-svg-icons'
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
33
import { getRandomString, joinObjectPathFragments, literal, objectPathGet } from '@sofie-automation/corelib/dist/lib'
4-
import React, { useCallback, useMemo, useState } from 'react'
4+
import React, { useCallback, useMemo } from 'react'
55
import { useTranslation } from 'react-i18next'
66
import {
77
WrappedOverridableItemNormal,
@@ -30,6 +30,8 @@ import { ObjectTableDeletedRow } from './ObjectTableDeletedRow'
3030
import { SchemaFormSectionHeader } from '../SchemaFormSectionHeader'
3131
import { UploadButton } from '../../uploadButton'
3232
import Tooltip from 'rc-tooltip'
33+
import { NoticeLevel, Notification, NotificationCenter } from '../../notifications/notifications'
34+
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
3335

3436
interface SchemaFormObjectTableProps {
3537
/** Schema for each row in the table */
@@ -293,8 +295,6 @@ interface ImportExportButtonsProps {
293295
function ImportExportButtons({ schema, overrideHelper, wrappedRows }: Readonly<ImportExportButtonsProps>) {
294296
const { t } = useTranslation()
295297

296-
const [uploadFileKey, setUploadFileKey] = useState(0)
297-
298298
const exportTable = () => {
299299
const exportObject: Record<string, any> = {}
300300
for (const obj of wrappedRows) {
@@ -319,73 +319,73 @@ function ImportExportButtons({ schema, overrideHelper, wrappedRows }: Readonly<I
319319
URL.revokeObjectURL(url)
320320
}
321321

322-
const importTable = (e: React.ChangeEvent<HTMLInputElement>) => {
323-
const file = e.target.files?.[0]
324-
if (!file) return
325-
326-
const reader = new FileReader()
327-
reader.onload = (e2) => {
328-
// On file upload
329-
setUploadFileKey(Date.now())
330-
331-
const uploadFileContents = e2.target?.result
332-
if (!uploadFileContents) return
333-
334-
const newRows = JSON.parse(uploadFileContents as string)
335-
if (!newRows || typeof newRows !== 'object') return
322+
const importTable = (uploadFileContents: string, file: File) => {
323+
const newRows = JSON.parse(uploadFileContents)
324+
if (!newRows || typeof newRows !== 'object') return
325+
326+
doModalDialog({
327+
title: t('Import file?'),
328+
yes: t('Replace rows'),
329+
no: t('Cancel'),
330+
message: (
331+
<p>
332+
{t('Are you sure you want to import the contents of the file "{{fileName}}"?', {
333+
fileName: file.name,
334+
})}
335+
</p>
336+
),
337+
onAccept: () => {
338+
const batch = overrideHelper()
339+
340+
for (const row of wrappedRows) {
341+
batch.deleteRow(row.id)
342+
}
336343

337-
doModalDialog({
338-
title: t('Import file?'),
339-
yes: t('Replace rows'),
340-
no: t('Cancel'),
341-
message: (
342-
<p>
343-
{t('Are you sure you want to import the contents of the file "{{fileName}}"?', {
344-
fileName: file.name,
345-
})}
346-
</p>
347-
),
348-
onAccept: () => {
349-
const batch = overrideHelper()
344+
for (const [rowId, row] of Object.entries<unknown>(newRows)) {
345+
batch.insertRow(rowId, row)
346+
}
350347

351-
for (const row of wrappedRows) {
352-
batch.deleteRow(row.id)
353-
}
348+
batch.commit()
349+
},
350+
actions: [
351+
{
352+
label: t('Append rows'),
353+
on: () => {
354+
const batch = overrideHelper()
354355

355-
for (const [rowId, row] of Object.entries<unknown>(newRows)) {
356-
batch.insertRow(rowId, row)
357-
}
356+
for (const [rowId, row] of Object.entries<unknown>(newRows)) {
357+
batch.insertRow(rowId, row)
358+
}
358359

359-
batch.commit()
360-
},
361-
actions: [
362-
{
363-
label: t('Append rows'),
364-
on: () => {
365-
const batch = overrideHelper()
366-
367-
for (const [rowId, row] of Object.entries<unknown>(newRows)) {
368-
batch.insertRow(rowId, row)
369-
}
370-
371-
batch.commit()
372-
},
373-
classNames: 'btn-secondary',
360+
batch.commit()
374361
},
375-
],
376-
})
377-
}
378-
reader.readAsText(file)
362+
classNames: 'btn-secondary',
363+
},
364+
],
365+
})
379366
}
367+
const importError = useCallback(
368+
(err: Error) => {
369+
NotificationCenter.push(
370+
new Notification(
371+
undefined,
372+
NoticeLevel.WARNING,
373+
t('Import error: {{errorMessage}}', { errorMessage: stringifyError(err) }),
374+
'ConfigManifestSettings'
375+
)
376+
)
377+
},
378+
[t]
379+
)
380380

381381
return (
382382
<>
383383
<Tooltip overlay={t('Import')} placement="top">
384384
<span className="inline-block">
385385
<UploadButton
386-
key={uploadFileKey}
387386
className="btn btn-secondary"
388-
onChange={importTable}
387+
onUploadContents={importTable}
388+
onUploadError={importError}
389389
accept="application/json,.json"
390390
>
391391
<FontAwesomeIcon icon={faUpload} />

packages/webui/src/client/lib/uploadButton.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,45 @@ import * as React from 'react'
33
interface IProps {
44
className?: string
55
accept?: string
6-
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
6+
onUploadContents: (contents: string, file: File) => void
7+
onUploadError: (err: Error) => void
78
children?: React.ReactNode
89
disabled?: boolean
910
}
1011

1112
export const UploadButton: React.FunctionComponent<IProps> = function (props: IProps) {
13+
const [timestampedFileKey, setTimestampedFileKey] = React.useState(0)
14+
15+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
16+
setTimestampedFileKey(Date.now())
17+
18+
const file = e.target.files?.[0]
19+
if (!file) return
20+
21+
const reader = new FileReader()
22+
23+
reader.onload = () => {
24+
const fileContents = reader.result as string
25+
26+
try {
27+
props.onUploadContents(fileContents, file)
28+
} catch (err: any) {
29+
props.onUploadError(err)
30+
}
31+
}
32+
reader.onerror = () => {
33+
props.onUploadError(new Error('Error reading file'))
34+
}
35+
reader.readAsText(file)
36+
}
1237
return (
1338
<label className={props.className}>
1439
{props.children}
1540
<input
41+
key={timestampedFileKey}
1642
type="file"
1743
accept={props.accept}
18-
onChange={props.onChange}
44+
onChange={handleChange}
1945
style={{
2046
visibility: 'hidden',
2147
width: 0,

0 commit comments

Comments
 (0)