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:
parent
b57b0bec88
commit
2e56896b05
9 changed files with 31 additions and 517 deletions
|
@ -82,7 +82,6 @@
|
||||||
"admin-ui": "file:",
|
"admin-ui": "file:",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"file-selector": "^1.1.0",
|
|
||||||
"flat": "^6.0.1",
|
"flat": "^6.0.1",
|
||||||
"i18next": "^23.16.3",
|
"i18next": "^23.16.3",
|
||||||
"i18next-http-backend": "^2.6.2",
|
"i18next-http-backend": "^2.6.2",
|
||||||
|
@ -92,7 +91,6 @@
|
||||||
"p-debounce": "^4.0.0",
|
"p-debounce": "^4.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.2.10",
|
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.0",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^6.27.0",
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
import { useState } from "react";
|
import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
|
||||||
import { useTranslation } from "react-i18next";
|
import { HelpItem, SelectControl } from "@keycloak/keycloak-ui-shared";
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
FormProvider,
|
|
||||||
useForm,
|
|
||||||
useFormContext,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
FileUpload,
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -16,12 +11,16 @@ import {
|
||||||
Text,
|
Text,
|
||||||
TextContent,
|
TextContent,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
|
import { useState } from "react";
|
||||||
import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig";
|
import {
|
||||||
import { HelpItem, SelectControl } from "@keycloak/keycloak-ui-shared";
|
Controller,
|
||||||
import { StoreSettings } from "./StoreSettings";
|
FormProvider,
|
||||||
import { FileUpload } from "../../components/json-file-upload/patternfly/FileUpload";
|
useForm,
|
||||||
|
useFormContext,
|
||||||
|
} from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||||
|
import { StoreSettings } from "./StoreSettings";
|
||||||
|
|
||||||
type GenerateKeyDialogProps = {
|
type GenerateKeyDialogProps = {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
|
@ -100,10 +99,10 @@ export const KeyForm = ({
|
||||||
value={field.value}
|
value={field.value}
|
||||||
filename={filename}
|
filename={filename}
|
||||||
browseButtonText={t("browse")}
|
browseButtonText={t("browse")}
|
||||||
onChange={(value, filename) => {
|
onTextChange={(value) => {
|
||||||
setFilename(filename);
|
|
||||||
field.onChange(value);
|
field.onChange(value);
|
||||||
}}
|
}}
|
||||||
|
onFileInputChange={(_, file) => setFilename(file.name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { SelectControl } from "@keycloak/keycloak-ui-shared";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
|
FileUpload,
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Modal,
|
Modal,
|
||||||
|
@ -10,8 +12,6 @@ import {
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
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 { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
||||||
import { StoreSettings } from "./StoreSettings";
|
import { StoreSettings } from "./StoreSettings";
|
||||||
|
|
||||||
|
@ -107,8 +107,11 @@ export const ImportKeyDialog = ({
|
||||||
id="importFile"
|
id="importFile"
|
||||||
value={field.value.value}
|
value={field.value.value}
|
||||||
filename={field.value.filename}
|
filename={field.value.filename}
|
||||||
onChange={(value, filename) =>
|
onTextChange={(value) =>
|
||||||
field.onChange({ value, filename })
|
field.onChange({ ...field.value, value })
|
||||||
|
}
|
||||||
|
onFileInputChange={(_, file) =>
|
||||||
|
field.onChange({ ...field.value, filename: file.name })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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 { useState } from "react";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
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 type { ComponentProps } from "./components";
|
||||||
import { convertToName } from "./DynamicComponents";
|
import { convertToName } from "./DynamicComponents";
|
||||||
|
|
||||||
|
@ -48,9 +46,8 @@ export const FileComponent = ({
|
||||||
}}
|
}}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
allowEditingUploadedText={false}
|
allowEditingUploadedText={false}
|
||||||
onChange={(value, filename) => {
|
onTextChange={(value) => {
|
||||||
field.onChange(value);
|
field.onChange(value);
|
||||||
setFilename(filename);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DropEvent,
|
||||||
|
FileUpload,
|
||||||
|
FileUploadProps,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
HelperText,
|
HelperText,
|
||||||
|
@ -14,9 +17,7 @@ import {
|
||||||
MouseEvent as ReactMouseEvent,
|
MouseEvent as ReactMouseEvent,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { DropEvent } from "react-dropzone";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FileUpload, FileUploadProps } from "./patternfly/FileUpload";
|
|
||||||
|
|
||||||
type FileUploadType = {
|
type FileUploadType = {
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -112,8 +113,8 @@ export const FileUploadForm = ({
|
||||||
value={fileUpload.value}
|
value={fileUpload.value}
|
||||||
filename={fileUpload.filename}
|
filename={fileUpload.filename}
|
||||||
onFileInputChange={handleFileInputChange}
|
onFileInputChange={handleFileInputChange}
|
||||||
onDataChange={handleTextOrDataChange}
|
onDataChange={(_, value) => handleTextOrDataChange(value)}
|
||||||
onTextChange={handleTextOrDataChange}
|
onTextChange={(_, value) => handleTextOrDataChange(value)}
|
||||||
onClearClick={handleClear}
|
onClearClick={handleClear}
|
||||||
onReadStarted={() =>
|
onReadStarted={() =>
|
||||||
setFileUpload({ ...fileUpload, isLoading: true })
|
setFileUpload({ ...fileUpload, isLoading: true })
|
||||||
|
@ -137,8 +138,8 @@ export const FileUploadForm = ({
|
||||||
value={fileUpload.value}
|
value={fileUpload.value}
|
||||||
filename={fileUpload.filename}
|
filename={fileUpload.filename}
|
||||||
onFileInputChange={handleFileInputChange}
|
onFileInputChange={handleFileInputChange}
|
||||||
onDataChange={handleTextOrDataChange}
|
onDataChange={(_, value) => handleTextOrDataChange(value)}
|
||||||
onTextChange={handleTextOrDataChange}
|
onTextChange={(_, value) => handleTextOrDataChange(value)}
|
||||||
onClearClick={handleClear}
|
onClearClick={handleClear}
|
||||||
onReadStarted={() =>
|
onReadStarted={() =>
|
||||||
setFileUpload({ ...fileUpload, isLoading: true })
|
setFileUpload({ ...fileUpload, isLoading: true })
|
||||||
|
|
|
@ -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";
|
|
|
@ -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";
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -180,9 +180,6 @@ importers:
|
||||||
file-saver:
|
file-saver:
|
||||||
specifier: ^2.0.5
|
specifier: ^2.0.5
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
file-selector:
|
|
||||||
specifier: ^1.1.0
|
|
||||||
version: 1.1.0
|
|
||||||
flat:
|
flat:
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
|
@ -210,9 +207,6 @@ importers:
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1(react@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:
|
react-hook-form:
|
||||||
specifier: ^7.53.1
|
specifier: ^7.53.1
|
||||||
version: 7.53.1(react@18.3.1)
|
version: 7.53.1(react@18.3.1)
|
||||||
|
@ -2822,10 +2816,6 @@ packages:
|
||||||
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
|
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
file-selector@1.1.0:
|
|
||||||
resolution: {integrity: sha512-242AM3EcVCRwKFqAHOVxuo93MnXnINrWR3tgEllLvBV3KhL2cWynQSReigbCwLNOkBaD5oyMeDCfXuSq1PlUzw==}
|
|
||||||
engines: {node: '>= 12'}
|
|
||||||
|
|
||||||
filing-cabinet@5.0.2:
|
filing-cabinet@5.0.2:
|
||||||
resolution: {integrity: sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==}
|
resolution: {integrity: sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==}
|
||||||
engines: {node: '>=18'}
|
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)
|
'@patternfly/react-table': 5.4.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
dagre: 0.8.5
|
dagre: 0.8.5
|
||||||
file-saver: 2.0.5
|
file-saver: 2.0.5
|
||||||
file-selector: 1.1.0
|
|
||||||
flat: 6.0.1
|
flat: 6.0.1
|
||||||
i18next: 23.16.3
|
i18next: 23.16.3
|
||||||
i18next-http-backend: 2.6.2
|
i18next-http-backend: 2.6.2
|
||||||
|
@ -5480,7 +5469,6 @@ snapshots:
|
||||||
p-debounce: 4.0.0
|
p-debounce: 4.0.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(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-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-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)
|
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:
|
dependencies:
|
||||||
tslib: 2.8.0
|
tslib: 2.8.0
|
||||||
|
|
||||||
file-selector@1.1.0:
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.0
|
|
||||||
|
|
||||||
filing-cabinet@5.0.2:
|
filing-cabinet@5.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
app-module-path: 2.2.0
|
app-module-path: 2.2.0
|
||||||
|
|
Loading…
Reference in a new issue