remove copied FileUpload component (#34290)

* remove copied FileUpload component

fixes: #32420
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* removed dependencies

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-10-25 15:27:47 +02:00 committed by GitHub
parent b57b0bec88
commit 2e56896b05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 31 additions and 517 deletions

View file

@ -82,7 +82,6 @@
"admin-ui": "file:",
"dagre": "^0.8.5",
"file-saver": "^2.0.5",
"file-selector": "^1.1.0",
"flat": "^6.0.1",
"i18next": "^23.16.3",
"i18next-http-backend": "^2.6.2",
@ -92,7 +91,6 @@
"p-debounce": "^4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.10",
"react-hook-form": "^7.53.1",
"react-i18next": "^15.1.0",
"react-router-dom": "^6.27.0",

View file

@ -1,14 +1,9 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
Controller,
FormProvider,
useForm,
useFormContext,
} from "react-hook-form";
import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
import { HelpItem, SelectControl } from "@keycloak/keycloak-ui-shared";
import {
Button,
ButtonVariant,
FileUpload,
Form,
FormGroup,
Modal,
@ -16,12 +11,16 @@ import {
Text,
TextContent,
} from "@patternfly/react-core";
import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
import { HelpItem, SelectControl } from "@keycloak/keycloak-ui-shared";
import { StoreSettings } from "./StoreSettings";
import { FileUpload } from "../../components/json-file-upload/patternfly/FileUpload";
import { useState } from "react";
import {
Controller,
FormProvider,
useForm,
useFormContext,
} from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { StoreSettings } from "./StoreSettings";
type GenerateKeyDialogProps = {
clientId: string;
@ -100,10 +99,10 @@ export const KeyForm = ({
value={field.value}
filename={filename}
browseButtonText={t("browse")}
onChange={(value, filename) => {
setFilename(filename);
onTextChange={(value) => {
field.onChange(value);
}}
onFileInputChange={(_, file) => setFilename(file.name)}
/>
)}
/>

View file

@ -1,6 +1,8 @@
import { SelectControl } from "@keycloak/keycloak-ui-shared";
import {
Button,
ButtonVariant,
FileUpload,
Form,
FormGroup,
Modal,
@ -10,8 +12,6 @@ import {
} from "@patternfly/react-core";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { SelectControl } from "@keycloak/keycloak-ui-shared";
import { FileUpload } from "../../components/json-file-upload/patternfly/FileUpload";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { StoreSettings } from "./StoreSettings";
@ -107,8 +107,11 @@ export const ImportKeyDialog = ({
id="importFile"
value={field.value.value}
filename={field.value.filename}
onChange={(value, filename) =>
field.onChange({ value, filename })
onTextChange={(value) =>
field.onChange({ ...field.value, value })
}
onFileInputChange={(_, file) =>
field.onChange({ ...field.value, filename: file.name })
}
/>
)}

View file

@ -1,10 +1,8 @@
import { FormGroup } from "@patternfly/react-core";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import { FileUpload, FormGroup } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import { FileUpload } from "../json-file-upload/patternfly/FileUpload";
import type { ComponentProps } from "./components";
import { convertToName } from "./DynamicComponents";
@ -48,9 +46,8 @@ export const FileComponent = ({
}}
isLoading={isLoading}
allowEditingUploadedText={false}
onChange={(value, filename) => {
onTextChange={(value) => {
field.onChange(value);
setFilename(filename);
}}
/>
)}

View file

@ -1,6 +1,9 @@
import { CodeEditor, Language } from "@patternfly/react-code-editor";
import {
Button,
DropEvent,
FileUpload,
FileUploadProps,
FormGroup,
FormHelperText,
HelperText,
@ -14,9 +17,7 @@ import {
MouseEvent as ReactMouseEvent,
useState,
} from "react";
import { DropEvent } from "react-dropzone";
import { useTranslation } from "react-i18next";
import { FileUpload, FileUploadProps } from "./patternfly/FileUpload";
type FileUploadType = {
value: string;
@ -112,8 +113,8 @@ export const FileUploadForm = ({
value={fileUpload.value}
filename={fileUpload.filename}
onFileInputChange={handleFileInputChange}
onDataChange={handleTextOrDataChange}
onTextChange={handleTextOrDataChange}
onDataChange={(_, value) => handleTextOrDataChange(value)}
onTextChange={(_, value) => handleTextOrDataChange(value)}
onClearClick={handleClear}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })
@ -137,8 +138,8 @@ export const FileUploadForm = ({
value={fileUpload.value}
filename={fileUpload.filename}
onFileInputChange={handleFileInputChange}
onDataChange={handleTextOrDataChange}
onTextChange={handleTextOrDataChange}
onDataChange={(_, value) => handleTextOrDataChange(value)}
onTextChange={(_, value) => handleTextOrDataChange(value)}
onClearClick={handleClear}
onReadStarted={() =>
setFileUpload({ ...fileUpload, isLoading: true })

View file

@ -1,224 +0,0 @@
import { fromEvent } from "file-selector";
import { PropsWithChildren } from "react";
import {
DropEvent,
DropzoneInputProps,
DropzoneOptions,
FileRejection,
useDropzone,
} from "react-dropzone";
import { FileUploadField, FileUploadFieldProps } from "./FileUploadField";
import { fileReaderType, readFile } from "./fileUtils";
export interface FileUploadProps
extends Omit<
FileUploadFieldProps,
| "children"
| "onBrowseButtonClick"
| "onClearButtonClick"
| "isDragActive"
| "containerRef"
> {
/** Unique id for the TextArea, also used to generate ids for accessible labels. */
id: string;
/** What type of file. Determines what is is passed to `onChange` and expected by `value`
* (a string for 'text' and 'dataURL', or a File object otherwise. */
type?: "text" | "dataURL";
/** Value of the file's contents
* (string if text file, File object otherwise) */
value?: string | File;
/** Value to be shown in the read-only filename field. */
filename?: string;
/** @deprecated A callback for when the file contents change. Please instead use onFileInputChange, onTextChange, onDataChange, onClearClick individually. */
onChange?: (
value: string | File,
filename: string,
event:
| React.MouseEvent<HTMLButtonElement, MouseEvent> // Clear button was clicked
| React.ChangeEvent<HTMLElement> // User typed in the TextArea
| DropEvent,
) => void;
/** Change event emitted from the hidden \<input type="file" \> field associated with the component */
onFileInputChange?: (event: DropEvent, file: File) => void;
/** Callback for clicking on the FileUploadField text area. By default, prevents a click in the text area from opening file dialog. */
onClick?: (event: React.MouseEvent) => void;
/** Additional classes added to the FileUpload container element. */
className?: string;
/** Flag to show if the field is disabled. */
isDisabled?: boolean;
/** Flag to show if the field is read only. */
isReadOnly?: boolean;
/** Flag to show if a file is being loaded. */
isLoading?: boolean;
/** Aria-valuetext for the loading spinner */
spinnerAriaValueText?: string;
/** Flag to show if the field is required. */
isRequired?: boolean;
/** Value to indicate if the field is modified to show that validation state.
* If set to success, field will be modified to indicate valid state.
* If set to error, field will be modified to indicate error state.
*/
validated?: "success" | "error" | "default";
/** Aria-label for the TextArea. */
"aria-label"?: string;
/** Placeholder string to display in the empty filename field */
filenamePlaceholder?: string;
/** Aria-label for the read-only filename field */
filenameAriaLabel?: string;
/** Text for the Browse button */
browseButtonText?: string;
/** Text for the Clear button */
clearButtonText?: string;
/** Flag to hide the built-in preview of the file (where available).
* If true, you can use children to render an alternate preview. */
hideDefaultPreview?: boolean;
/** Flag to allow editing of a text file's contents after it is selected from disk */
allowEditingUploadedText?: boolean;
/** Additional children to render after (or instead of) the file preview. */
children?: React.ReactNode;
// Props available in FileUpload but not FileUploadField:
/** A callback for when a selected file starts loading */
onReadStarted?: (fileHandle: File) => void;
/** A callback for when a selected file finishes loading */
onReadFinished?: (fileHandle: File) => void;
/** A callback for when the FileReader API fails */
onReadFailed?: (error: DOMException, fileHandle: File) => void;
/** Optional extra props to customize react-dropzone. */
dropzoneProps?: DropzoneOptions;
/** Clear button was clicked */
onClearClick?: React.MouseEventHandler<HTMLButtonElement>;
/** Text area text changed */
onTextChange?: (text: string) => void;
/** On data changed - if type='text' or type='dataURL' and file was loaded it will call this method */
onDataChange?: (data: string) => void;
}
export const FileUpload = ({
id,
type,
value = type === fileReaderType.text || type === fileReaderType.dataURL
? ""
: undefined,
filename = "",
children = null,
// TODO: This should be removed as part of https://github.com/keycloak/keycloak/issues/32420
// eslint-disable-next-line @typescript-eslint/no-deprecated
onChange,
onFileInputChange,
onReadStarted,
onReadFinished,
onReadFailed,
onClearClick,
onClick = (event) => event.preventDefault(),
onTextChange,
onDataChange,
dropzoneProps = {},
...props
}: PropsWithChildren<FileUploadProps>) => {
const onDropAccepted = (acceptedFiles: File[], event: DropEvent) => {
if (acceptedFiles.length > 0) {
const fileHandle = acceptedFiles[0];
if (event?.type === "drop") {
onFileInputChange?.(event, fileHandle);
}
if (type === fileReaderType.text || type === fileReaderType.dataURL) {
onChange?.("", fileHandle.name, event); // Show the filename while reading
onReadStarted?.(fileHandle);
readFile(fileHandle, type as fileReaderType)
.then((data) => {
onReadFinished?.(fileHandle);
onChange?.(data as string, fileHandle.name, event);
onDataChange?.(data as string);
})
.catch((error: DOMException) => {
onReadFailed?.(error, fileHandle);
onReadFinished?.(fileHandle);
onChange?.("", "", event); // Clear the filename field on a failure
onDataChange?.("");
});
} else {
onChange?.(fileHandle, fileHandle.name, event);
}
}
dropzoneProps.onDropAccepted?.(acceptedFiles, event);
};
const onDropRejected = (rejectedFiles: FileRejection[], event: DropEvent) => {
if (rejectedFiles.length > 0) {
onChange?.("", rejectedFiles[0].file.name, event);
}
dropzoneProps.onDropRejected?.(rejectedFiles, event);
};
const onClearButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => {
onChange?.("", "", event);
onClearClick?.(event);
setFileValue("");
};
const { getRootProps, getInputProps, isDragActive, open, inputRef } =
useDropzone({
multiple: false,
...dropzoneProps,
onDropAccepted,
onDropRejected,
});
const setFileValue = (filename: string) => {
if (!inputRef.current) {
return;
}
inputRef.current.value = filename;
};
const oldInputProps = getInputProps();
const inputProps: DropzoneInputProps = {
...oldInputProps,
onChange: async (e: React.ChangeEvent<HTMLInputElement>) => {
oldInputProps.onChange?.(e);
const files = await fromEvent(e.nativeEvent);
if (files.length === 1) {
onFileInputChange?.(e, files[0] as File);
}
},
};
return (
<FileUploadField
{...getRootProps({
...props,
refKey: "containerRef",
onClick: (event) => event.preventDefault(),
})}
tabIndex={undefined} // Omit the unwanted tabIndex from react-dropzone's getRootProps
id={id}
type={type}
filename={filename}
value={value}
onChange={onChange}
isDragActive={isDragActive}
onBrowseButtonClick={open}
onClearButtonClick={onClearButtonClick}
onTextAreaClick={onClick}
onTextChange={onTextChange}
onClick={(e) => e.stopPropagation()}
>
<input
/* hidden, necessary for react-dropzone */
{...inputProps}
ref={inputRef}
/>
{children}
</FileUploadField>
);
};
FileUpload.displayName = "FileUpload";

View file

@ -1,213 +0,0 @@
import {
Button,
ButtonVariant,
InputGroup,
Spinner,
spinnerSize,
TextArea,
TextAreResizeOrientation,
TextInput,
InputGroupItem,
} from "@patternfly/react-core";
import { css } from "@patternfly/react-styles";
import styles from "@patternfly/react-styles/css/components/FileUpload/file-upload";
import { PropsWithChildren } from "react";
import { fileReaderType } from "./fileUtils";
export interface FileUploadFieldProps
extends Omit<React.HTMLProps<HTMLDivElement>, "value" | "onChange"> {
/** Unique id for the TextArea, also used to generate ids for accessible labels */
id: string;
/** What type of file. Determines what is is expected by `value`
* (a string for 'text' and 'dataURL', or a File object otherwise). */
type?: "text" | "dataURL";
/** Value of the file's contents
* (string if text file, File object otherwise) */
value?: string | File;
/** Value to be shown in the read-only filename field. */
filename?: string;
/** A callback for when the TextArea value changes. */
onChange?: (
value: string,
filename: string,
event:
| React.ChangeEvent<HTMLTextAreaElement> // User typed in the TextArea
| React.MouseEvent<HTMLButtonElement, MouseEvent>, // User clicked Clear button
) => void;
/** Additional classes added to the FileUploadField container element. */
className?: string;
/** Flag to show if the field is disabled. */
isDisabled?: boolean;
/** Flag to show if the field is read only. */
isReadOnly?: boolean;
/** Flag to show if a file is being loaded. */
isLoading?: boolean;
/** Aria-valuetext for the loading spinner */
spinnerAriaValueText?: string;
/** Flag to show if the field is required. */
isRequired?: boolean;
/** Value to indicate if the field is modified to show that validation state.
* If set to success, field will be modified to indicate valid state.
* If set to error, field will be modified to indicate error state.
*/
validated?: "success" | "error" | "default";
/** Aria-label for the TextArea. */
"aria-label"?: string;
/** Placeholder string to display in the empty filename field */
filenamePlaceholder?: string;
/** Aria-label for the read-only filename field */
filenameAriaLabel?: string;
/** Text for the Browse button */
browseButtonText?: string;
/** Text for the Clear button */
clearButtonText?: string;
/** Flag to disable the Clear button */
isClearButtonDisabled?: boolean;
/** Flag to hide the built-in preview of the file (where available).
* If true, you can use children to render an alternate preview. */
hideDefaultPreview?: boolean;
/** Flag to allow editing of a text file's contents after it is selected from disk */
allowEditingUploadedText?: boolean;
/** Additional children to render after (or instead of) the file preview. */
children?: React.ReactNode;
// Props available in FileUploadField but not FileUpload:
/** A callback for when the Browse button is clicked. */
onBrowseButtonClick?: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => void;
/** A callback for when the Clear button is clicked. */
onClearButtonClick?: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => void;
/** A callback from when the text area is clicked. Can also be set via the onClick property of FileUpload. */
onTextAreaClick?: (
event: React.MouseEvent<HTMLTextAreaElement, MouseEvent>,
) => void;
/** Flag to show if a file is being dragged over the field */
isDragActive?: boolean;
/** A reference object to attach to the FileUploadField container element. */
containerRef?: React.Ref<HTMLDivElement>;
/** Text area text changed */
onTextChange?: (text: string) => void;
}
export const FileUploadField = ({
id,
type,
value = "",
filename = "",
onChange,
onBrowseButtonClick,
onClearButtonClick,
onTextAreaClick,
onTextChange,
className = "",
isDisabled = false,
isReadOnly = false,
isLoading = false,
spinnerAriaValueText,
isRequired = false,
isDragActive = false,
validated = "default" as "success" | "error" | "default",
"aria-label": ariaLabel = "File upload",
filenamePlaceholder = "Drag a file here or browse to upload",
filenameAriaLabel = filename ? "Read only filename" : filenamePlaceholder,
browseButtonText = "Browse...",
clearButtonText = "Clear",
isClearButtonDisabled = !filename && !value,
containerRef = null as React.Ref<HTMLDivElement>,
allowEditingUploadedText = false,
hideDefaultPreview = false,
children = null,
...props
}: PropsWithChildren<FileUploadFieldProps>) => {
const onTextAreaChange = (
newValue: string,
event: React.ChangeEvent<HTMLTextAreaElement>,
) => {
onChange?.(newValue, filename, event);
onTextChange?.(newValue);
};
return (
<div
className={css(
styles.fileUpload,
isDragActive && styles.modifiers.dragHover,
isLoading && styles.modifiers.loading,
className,
)}
ref={containerRef}
{...props}
>
<div className={styles.fileUploadFileSelect}>
<InputGroup>
<InputGroupItem isFill>
<TextInput
// Always read-only regardless of isReadOnly prop (which is just for the TextArea)
isDisabled={isDisabled}
id={`${id}-filename`}
name={`${id}-filename`}
aria-label={filenameAriaLabel}
placeholder={filenamePlaceholder}
aria-describedby={`${id}-browse-button`}
value={filename}
readOnlyVariant="default"
/>
</InputGroupItem>
<InputGroupItem>
<Button
id={`${id}-browse-button`}
variant={ButtonVariant.control}
onClick={onBrowseButtonClick}
isDisabled={isDisabled}
>
{browseButtonText}
</Button>
</InputGroupItem>
<InputGroupItem>
<Button
variant={ButtonVariant.control}
isDisabled={isDisabled || isClearButtonDisabled}
onClick={onClearButtonClick}
>
{clearButtonText}
</Button>
</InputGroupItem>
</InputGroup>
</div>
<div className={styles.fileUploadFileDetails}>
{!hideDefaultPreview && type === fileReaderType.text && (
<TextArea
readOnly={isReadOnly || (!!filename && !allowEditingUploadedText)}
disabled={isDisabled}
isRequired={isRequired}
resizeOrientation={TextAreResizeOrientation.vertical}
validated={validated}
id={id}
name={id}
aria-label={ariaLabel}
value={value as string}
onChange={(event, newValue: string) =>
onTextAreaChange(newValue, event)
}
onClick={onTextAreaClick}
/>
)}
{isLoading && (
<div className={styles.fileUploadFileDetailsSpinner}>
<Spinner
size={spinnerSize.lg}
aria-valuetext={spinnerAriaValueText}
/>
</div>
)}
</div>
{children}
</div>
);
};
FileUploadField.displayName = "FileUploadField";

View file

@ -1,31 +0,0 @@
export enum fileReaderType {
text = "text",
dataURL = "dataURL",
}
/**
* Read a file using the FileReader API, either as a plain text string or as a DataURL string.
* Returns a promise which will resolve with the file contents as a string or reject with a DOMException.
*
* @param {File} fileHandle - File object to read
* @param {fileReaderType} type - How to read it
*/
export function readFile(fileHandle: File, type: fileReaderType) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
switch (type) {
case fileReaderType.text:
reader.readAsText(fileHandle);
break;
case fileReaderType.dataURL:
reader.readAsDataURL(fileHandle);
break;
default:
reject("unknown type");
}
});
}

View file

@ -180,9 +180,6 @@ importers:
file-saver:
specifier: ^2.0.5
version: 2.0.5
file-selector:
specifier: ^1.1.0
version: 1.1.0
flat:
specifier: ^6.0.1
version: 6.0.1
@ -210,9 +207,6 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
react-dropzone:
specifier: ^14.2.10
version: 14.2.10(react@18.3.1)
react-hook-form:
specifier: ^7.53.1
version: 7.53.1(react@18.3.1)
@ -2822,10 +2816,6 @@ packages:
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
engines: {node: '>= 12'}
file-selector@1.1.0:
resolution: {integrity: sha512-242AM3EcVCRwKFqAHOVxuo93MnXnINrWR3tgEllLvBV3KhL2cWynQSReigbCwLNOkBaD5oyMeDCfXuSq1PlUzw==}
engines: {node: '>= 12'}
filing-cabinet@5.0.2:
resolution: {integrity: sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==}
engines: {node: '>=18'}
@ -5470,7 +5460,6 @@ snapshots:
'@patternfly/react-table': 5.4.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
dagre: 0.8.5
file-saver: 2.0.5
file-selector: 1.1.0
flat: 6.0.1
i18next: 23.16.3
i18next-http-backend: 2.6.2
@ -5480,7 +5469,6 @@ snapshots:
p-debounce: 4.0.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-dropzone: 14.2.10(react@18.3.1)
react-hook-form: 7.53.1(react@18.3.1)
react-i18next: 15.1.0(i18next@23.16.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router-dom: 6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -7594,10 +7582,6 @@ snapshots:
dependencies:
tslib: 2.8.0
file-selector@1.1.0:
dependencies:
tslib: 2.8.0
filing-cabinet@5.0.2:
dependencies:
app-module-path: 2.2.0