From e9fd83755943b0998ac56bd985f357fd688aaa21 Mon Sep 17 00:00:00 2001 From: Jose Manuel Serrano Amaut <a20122128@pucp.pe> Date: Fri, 24 Feb 2023 00:11:06 -0500 Subject: [PATCH] [REF]: Change css rules for changing height and width accordint to windows width, allso delete padding in container --- bugs.md | 25 + features.md | 36 ++ future-features.md | 29 ++ ideas.md | 18 + src/components/MainMenu/MainMenuSideBar.tsx | 25 +- src/components/MainPage/MainFooter.jsx | 2 +- src/components/MainPage/MainNavBar.jsx | 25 +- .../FileMosaicImageVideoPreviews.tsx | 4 +- .../avatar-demo/BasicDemoAvatar.tsx | 43 ++ src/files-ui/components/avatar/Avatar.scss | 32 +- src/files-ui/components/avatar/Avatar.tsx | 79 ++- src/files-ui/components/avatar/AvatarProps.ts | 15 +- src/files-ui/components/avatar/index.ts | 2 + .../components/avatar/useAvatarStyle.ts | 31 +- .../dropzone/components/dropzone/Dropzone.tsx | 45 +- .../components/file-mosaic/FileMosaic.tsx | 1 - .../components/icons/IconProps/IconProps.ts | 2 +- src/files-ui/components/icons/utils/utils.ts | 2 + src/files-ui/components/index.ts | 3 + .../LoaderContainer/LoaderContainerProps.ts | 2 +- .../previews/FullScreen/FullScreen.scss | 37 +- .../previews/FullScreen/FullScreen.tsx | 24 +- .../previews/ImagePreview/ImagePreview.tsx | 41 +- .../ImagePreview/ImagePreviewProps.ts | 7 + src/files-ui/core/index.ts | 18 +- .../core/upload/addExtraData.upload.ts | 17 + src/files-ui/core/upload/addheaders.upload.ts | 17 + src/files-ui/core/upload/errors.upload.ts | 37 ++ src/files-ui/core/upload/index.ts | 31 +- src/files-ui/core/upload/response.upload.ts | 61 +++ src/files-ui/core/upload/upload.ts | 485 +++--------------- src/files-ui/core/upload/upload.utils.ts | 253 --------- src/files-ui/core/upload/utils.upload.ts | 117 +++++ src/pages/api/AvatarApi.jsx | 10 + src/pages/api/FileMosaicApi.jsx | 4 +- src/pages/api/InputButtonApi.jsx | 10 + src/pages/demo/AvatarDemoPage.tsx | 146 ++++++ src/pages/demo/DropzoneDemoPage.jsx | 107 ++-- src/pages/demo/FileCardDemoPage.jsx | 18 +- src/pages/demo/FileMosaicDemoPage.jsx | 22 +- src/pages/demo/InputButtonDemoPage.tsx | 10 + .../getting-started/GettingStartedPage.jsx | 11 +- src/pages/usage/UsagePage.jsx | 8 +- src/routes/MainRouter.jsx | 20 + ...ft.VisualStudio.Services.Icons.Default.png | Bin 0 -> 3546 bytes src/static/new-icons/gradle.jpg | Bin 0 -> 4879 bytes src/static/new-icons/images.png | Bin 0 -> 892 bytes src/static/new-icons/markdown-fill-1.png | Bin 0 -> 5937 bytes src/static/new-icons/maven_logo.png | Bin 0 -> 20606 bytes .../other/Captura de pantalla (3946).png | Bin ref mt.png => src/static/other/ref mt.png | Bin src/templates/NavBarTemplate.jsx | 4 +- src/tester/AvatarTester.tsx | 80 +++ use-cases.md | 2 + 54 files changed, 1133 insertions(+), 885 deletions(-) create mode 100644 bugs.md create mode 100644 features.md create mode 100644 future-features.md create mode 100644 ideas.md create mode 100644 src/components/demo-components/avatar-demo/BasicDemoAvatar.tsx create mode 100644 src/files-ui/components/avatar/index.ts create mode 100644 src/files-ui/core/upload/addExtraData.upload.ts create mode 100644 src/files-ui/core/upload/addheaders.upload.ts create mode 100644 src/files-ui/core/upload/errors.upload.ts create mode 100644 src/files-ui/core/upload/response.upload.ts delete mode 100644 src/files-ui/core/upload/upload.utils.ts create mode 100644 src/files-ui/core/upload/utils.upload.ts create mode 100644 src/pages/api/AvatarApi.jsx create mode 100644 src/pages/api/InputButtonApi.jsx create mode 100644 src/pages/demo/AvatarDemoPage.tsx create mode 100644 src/pages/demo/InputButtonDemoPage.tsx create mode 100644 src/static/new-icons/Microsoft.VisualStudio.Services.Icons.Default.png create mode 100644 src/static/new-icons/gradle.jpg create mode 100644 src/static/new-icons/images.png create mode 100644 src/static/new-icons/markdown-fill-1.png create mode 100644 src/static/new-icons/maven_logo.png rename Captura de pantalla (3946).png => src/static/other/Captura de pantalla (3946).png (100%) rename ref mt.png => src/static/other/ref mt.png (100%) create mode 100644 src/tester/AvatarTester.tsx create mode 100644 use-cases.md diff --git a/bugs.md b/bugs.md new file mode 100644 index 0000000..6ff2e90 --- /dev/null +++ b/bugs.md @@ -0,0 +1,25 @@ +# BUGS + +## File Item (mosaic) + +- After uploading, progress must be reiitialized to 0 +- [SOLVED] FileiTEMmAINlAYER WORKS STRANGE AT THE TIME NEW fILEiTEM IS ADDED +- Fileptions (menu collapsed from click in option icon) + +## Dropzone + +- [SOLVED]: Uploading works in 2 times (first time stops after setting progress = UPLOADING.progrss), but fails to recover from the manager. [UPDATED]: Problem is at `useDropzoneFileListUpdater.ts` file. The problem is that hook for updating when user wants to interrupt preparing, is called at the begining of the upload process, with value of undefined in all files. It is probably the last update on localFiles outside Dropzone component. + +- When file is set from preparing to undefined it can be deleted, however, will appear again if onDelete is called. It would be great to add a reconciliation procedure to support different array sizes in updater hook. Or "canceled" upload status could be added and file Item should not show the "X" button when uploading and canceled. After Upload process, all files with "canceled" upload status should be set to "undefined" again. This can be a workaround. + +- [SOLVED] Dropzpne is afected by adding more files as children +- offset should not be used, instead a padding, given header or footer +- When layer is visible, border in the root container must dissapear + +## Tooltip + +- prints console.log() every time I hover FileItem, even when no message is sent + +## Drop Layer + +- prints the classname everytime I drop files o select files diff --git a/features.md b/features.md new file mode 100644 index 0000000..3247d94 --- /dev/null +++ b/features.md @@ -0,0 +1,36 @@ +# Files-ui Features + +## Upload to a server + +- Upload File object +- Upload Form Data +- Upload ExtFile +- Upload with custom headers +- Upload with additional data +- Upload adding callbacks for progress and abort and load + +## Valid + +- Validate with accept prop +- Validate with maxFileSize prop +- Validate with maxFiles prop + +## Read + +- Read as URL +- Read as text +- Read as base64 +- Read adding callbacks for abort and progress and load +- reduce the size of an image (not size , but resolution, other word) + +## UI Components + +- Dropzone +- InputButon +- Avatar +- ImagePreview +- VideoPreview +- PDFPreview +- JsonPreview fix rc-highlight +- FullScreenPreview +- DropLayer diff --git a/future-features.md b/future-features.md new file mode 100644 index 0000000..7ca731f --- /dev/null +++ b/future-features.md @@ -0,0 +1,29 @@ +# Future Possible Features + +## UTILS + +- custom icons +- menu icon for FileItem +- Outside actions or buttons for Dropzone +- header and footer custom props +- FileItem: checkbox support +- FileItem: detect when width of image is greater than height or viceversa in order to decide the orientation + +## Upload + +- upload multiple files +- chuncked uploads +- upload concurrent + +## Integrations upload + +- aws S3 +- azure +- google cloud platform (drive) +- dropbox +- firebase +- Java spring + +## dont know if context would be a good idea +- maybe yes for props like custom buttons for file mosaci +- custom file thumbnails diff --git a/ideas.md b/ideas.md new file mode 100644 index 0000000..9fe809d --- /dev/null +++ b/ideas.md @@ -0,0 +1,18 @@ +# Files UI Ideas + +## video + +-small videos for each feature tutorial, after compilig .... "unstopable from Sia" + +- dragon ball OST + +## Phrases + +- Stop pain with developing a complex widget, don't need to create a file upload component from scratch +- If you need to do it from scratch, there is an example [show some basic code] + + + +## MIgrating from dropzone-ui + +## Migrating from react-dropzone diff --git a/src/components/MainMenu/MainMenuSideBar.tsx b/src/components/MainMenu/MainMenuSideBar.tsx index 8c0c3eb..74e6a1e 100644 --- a/src/components/MainMenu/MainMenuSideBar.tsx +++ b/src/components/MainMenu/MainMenuSideBar.tsx @@ -46,15 +46,20 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { onClick: () => navigate("/components/filemosaic"), }, { - label: "FileInputButton", + label: "InputButton", index: 23, - onClick: () => navigate("/components/fileinputbutton"), + onClick: () => navigate("/components/inputbutton"), }, { label: "FileCard", index: 24, onClick: () => navigate("/components/filecard"), }, + { + label: "Avatar", + index: 25, + onClick: () => navigate("/components/avatar"), + }, ], }, { @@ -75,9 +80,9 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { onClick: () => navigate("/api/filemosaic"), }, { - label: "FileInputButton", + label: "InputButton", index: 33, - onClick: () => navigate("/api/fileinputbutton"), + onClick: () => navigate("/api/inputbutton"), }, { label: "FileCard", @@ -99,6 +104,11 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { index: 34, onClick: () => navigate("/api/videopreview"), }, + { + label: "Avatar", + index: 35, + onClick: () => navigate("/api/avatar"), + }, ], }, { @@ -223,7 +233,7 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { {subMenu && ( <Collapse - in={isOpen } + in={isOpen} timeout="auto" unmountOnExit key={"collapse-submenu" + indexBase} @@ -268,7 +278,8 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { <ListItemButton style={{ padding: "2px 20px" }} key={indexBase} - selected={subMenu === undefined && selectedIndex === index} + // selected={subMenu === undefined && selectedIndex === index} + selected={isOpen && selectedIndex === index} onClick={(event) => handleListItemClick( event, @@ -293,7 +304,7 @@ export default function MainMenuSideBar(props: MainMenuSideBarProps) { {subMenu && ( <Collapse - in={isOpen } + in={isOpen} timeout="auto" unmountOnExit key={"collapse-submenu" + indexBase} diff --git a/src/components/MainPage/MainFooter.jsx b/src/components/MainPage/MainFooter.jsx index b73c575..e91afb0 100644 --- a/src/components/MainPage/MainFooter.jsx +++ b/src/components/MainPage/MainFooter.jsx @@ -9,7 +9,7 @@ const MainFooter = (props) => { style={{ display:"flex", flexDirection:"row", alignItems:"center" }} > <img className="fui-logo-img" src={logo_blue} width="38px" /> - <img className="fui-logo-text-img" src={logo_white_ext} height="18px" /> + <img className="fui-logo-text-img" src={logo_white_ext} height="14px" /> </div> <p>{" | "}Copyright © 2023</p> </footer> diff --git a/src/components/MainPage/MainNavBar.jsx b/src/components/MainPage/MainNavBar.jsx index 5a6a0ef..e7142fd 100644 --- a/src/components/MainPage/MainNavBar.jsx +++ b/src/components/MainPage/MainNavBar.jsx @@ -6,9 +6,12 @@ import { IconButton, Tooltip, Typography } from "@mui/material"; import logo_text_blue from "../../static/files-ui-logo-text-med.png"; import logo_text_blue_dark from "../../static/files-ui-logo-text-med-dark.png"; - - -const MainNavBar = ({ darkModeOn, logo_blue, logo_blue_dark, handleDarkMode }) => { +const MainNavBar = ({ + darkModeOn, + logo_blue, + logo_blue_dark, + handleDarkMode, +}) => { const handleGoGitRepo = () => { window.open("https://github.com/files-ui", "_blank"); }; @@ -17,27 +20,17 @@ const MainNavBar = ({ darkModeOn, logo_blue, logo_blue_dark, handleDarkMode }) = <nav className="filesui-nav"> <div className="filesui-nav-container"> <div className="left-part"> - <div - className={ - "filesui-nav-logo-container" - } - > + <div className={"filesui-nav-logo-container"}> <img className={"filesui-nav-logo"} // src={!darkModeOn ? logo_blue : logoLight} - src={darkModeOn?logo_blue_dark:logo_blue} + src={darkModeOn ? logo_blue_dark : logo_blue} alt="files-ui-main-logo" /> </div> - {/* <Typography variant="h5" noWrap component="div" color="primary"> - Files ui - </Typography> */} - {/* <p className="filesui-nav-text-logo"> - <span className="gradient-span">Files UI</span> - </p> */} <img - height={"20px"} + height={"18px"} src={darkModeOn ? logo_text_blue_dark : logo_text_blue} alt="files-ui-main-logo-text" /> diff --git a/src/components/MainPage/MainRight/FileMosaicImageVideoPreviews.tsx b/src/components/MainPage/MainRight/FileMosaicImageVideoPreviews.tsx index 7b9428d..9cb19d3 100644 --- a/src/components/MainPage/MainRight/FileMosaicImageVideoPreviews.tsx +++ b/src/components/MainPage/MainRight/FileMosaicImageVideoPreviews.tsx @@ -30,7 +30,9 @@ const FileMosaicImageVideoPreviews: React.FC< "https://files-ui-temp-storage.s3.amazonaws.com/2029385a4ed32ff10beeb94c0585e8ac1a8c377c68d22ef25ce5863694a5499e.mp4" ); //setVideoSrc(videoSource); - setVideoSrc("https://files-ui-temp-storage.s3.amazonaws.com/2029385a4ed32ff10beeb94c0585e8ac1a8c377c68d22ef25ce5863694a5499e.mp4"); + // + setVideoSrc("https://files-ui-temp-storage.s3.amazonaws.com/2029385a4ed32ff10beeb94c0585e8ac1a8c377c68d22ef25ce5863694a5499e.mp4"); + // setVideoSrc("https://www.w3schools.com/tags/movie.mp4"); }; return ( diff --git a/src/components/demo-components/avatar-demo/BasicDemoAvatar.tsx b/src/components/demo-components/avatar-demo/BasicDemoAvatar.tsx new file mode 100644 index 0000000..b64cf95 --- /dev/null +++ b/src/components/demo-components/avatar-demo/BasicDemoAvatar.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { Avatar } from "../../../files-ui"; +import { ServerResponse, uploadFile } from "../../../files-ui/core"; +const REMOTE = + "https://files-ui-express-static-file-storage.vercel.app/39d33dff2d41b522c1ea276c4b82507f96b9699493d2e7b3f5c864ba743d9503"; + +const BasicDemoAvatar = () => { + const [avatarSrc, setAvatarSrc] = React.useState<string | undefined>( + "https://files-ui-temp-storage.s3.amazonaws.com/3b3b28b79c49f52ef1d89a35337797532b9cf4b5f3a00678e6f775c974dfbd56.png" + ); + const [isUloading, setIsUploading] = React.useState<boolean>(false); + + const handleChange2 = async (file: File) => { + const endpoint: string = REMOTE + "/file/28048465460"; + setIsUploading(true); + try { + const res: ServerResponse = await uploadFile(file, endpoint); + if (!res.success) alert(res.message); + else { + const { URL } = res.payload; + setAvatarSrc(URL); + } + setIsUploading(false); + } catch (error) { + console.log("ERROR:", error); + alert("ERROR ON UPLOAD"); + setIsUploading(false); + } + }; + + return ( + <React.Fragment> + <Avatar + src={avatarSrc} + //variant="circle" + style={{ width: "280px", height: "280px" }} + onChange={handleChange2} + isUloading={isUloading} + /> + </React.Fragment> + ); +}; +export default BasicDemoAvatar; diff --git a/src/files-ui/components/avatar/Avatar.scss b/src/files-ui/components/avatar/Avatar.scss index afd13e3..d219ea8 100644 --- a/src/files-ui/components/avatar/Avatar.scss +++ b/src/files-ui/components/avatar/Avatar.scss @@ -3,18 +3,24 @@ height: 200px; position: relative; background-color: transparent; - + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(128, 128, 128, 0.486); + border-radius: 10px; + &.square { + border-radius: 0px; + } + &.circle { + border-radius: 50%; + } .fui-avatar-image { - &.square { - border-radius: 10px; - } - overflow: hidden; - background-color: rgba(128, 128, 128, 0.486); - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; + //width: 100%; + //height: 100%; + background-repeat: no-repeat; + background-size: cover; + background-position: center; } &:hover { .fui-avatar-label { @@ -24,9 +30,6 @@ } } .fui-avatar-label { - &.square { - border-radius: 10px; - } &.hide { display: none; } @@ -43,6 +46,7 @@ align-items: center; justify-content: center; text-align: center; + flex-direction: column; &:hover { background-color: rgba(71, 71, 71, 0.74); display: flex; diff --git a/src/files-ui/components/avatar/Avatar.tsx b/src/files-ui/components/avatar/Avatar.tsx index 42b2bd6..57dc598 100644 --- a/src/files-ui/components/avatar/Avatar.tsx +++ b/src/files-ui/components/avatar/Avatar.tsx @@ -9,6 +9,9 @@ import { import InputHidden from "../input-hidden/InputHidden"; import { useAvatarStyle } from "./useAvatarStyle"; import { DynamicSheet, DynamiCSS } from "@dynamicss/dynamicss"; +import { ImagePreview } from "../previews"; +import InfiniteLoader from "../loader/InfiniteLoader/InfiniteLoader"; +import Layer from "../file-mosaic/components/file-mosaic-layer/Layer"; const Avatar: React.FC<AvatarProps> = (props: AvatarProps) => { const { style, @@ -20,7 +23,10 @@ const Avatar: React.FC<AvatarProps> = (props: AvatarProps) => { readOnly, variant, borderRadius, + uploadingLabel, + isUloading, onError, + smart, } = mergeProps(props, defaultAvatarProps); const inputRef: React.RefObject<HTMLInputElement> = @@ -28,6 +34,8 @@ const Avatar: React.FC<AvatarProps> = (props: AvatarProps) => { const isStyleInjected: boolean = useAvatarStyle(borderRadius); + //const [isUloading, setIsUploading] = React.useState<boolean>(false); + const avatarClassNameContainer: string = setAvatarClassNameContainer(variant); const avatarClassNameLayerInfo: string = setAvatarClassNameLayerInfo(variant); @@ -56,33 +64,50 @@ const Avatar: React.FC<AvatarProps> = (props: AvatarProps) => { if (isStyleInjected) { return ( - <div className="fui-avatar-main-container" style={style}> - {/**Layer 1 */} - {src ? ( - <img - className="fui-avatar-image" - height={"100%"} - width={"100%"} - src={src} - alt={alt} - onError={handleError} - /> - ) : ( - <p className={"fui-avatar-label"}>{emptyLabel}</p> - )} - {/**Layer 2 */} - {!readOnly && ( - <p className={"fui-avatar-label hide"} onClick={handleClick}> - {src ? changeLabel : emptyLabel} - <InputHidden - multiple={false} - accept={"image/*"} - onChange={handleChangeInput} - inputRef={inputRef} - />{" "} - </p> - )} - </div> + <React.Fragment> + <div + className={`fui-avatar-main-container${ + variant === "circle" ? " circle" : "" + }`} + style={style} + > + {/**Layer 1 */} + {isUloading ? ( + <Layer visible={isUloading}> + <div className={"fui-avatar-label"}> + <InfiniteLoader /> + {uploadingLabel} + </div> + </Layer> + ) : src ? ( + <> + <ImagePreview + className={`fui-avatar-image`} + src={src} + alt={alt} + onError={handleError} + smart={smart} + /> + </> + ) : ( + <div className={"fui-avatar-label"}>{emptyLabel}</div> + )} + {/**Layer 2 */} + {!readOnly && ( + <> + <p className={"fui-avatar-label hide"} onClick={handleClick}> + {src ? changeLabel : emptyLabel} + </p> + <InputHidden + multiple={false} + accept={""} + onChange={handleChangeInput} + inputRef={inputRef} + /> + </> + )} + </div> + </React.Fragment> ); } return <React.Fragment></React.Fragment>; diff --git a/src/files-ui/components/avatar/AvatarProps.ts b/src/files-ui/components/avatar/AvatarProps.ts index 6280923..55e484a 100644 --- a/src/files-ui/components/avatar/AvatarProps.ts +++ b/src/files-ui/components/avatar/AvatarProps.ts @@ -11,15 +11,24 @@ export interface AvatarFullProps extends OverridableComponentProps { alt?: string, emptyLabel?: string; + uploadingLabel?: string; changeLabel?: string; /** - * if a src given, then avanatr will show the image + * if a src is given, then avatar will show the image * or a file error message and will not allow * the user to change the picture. Also, layer on hover will not be shown */ readOnly?: boolean; + isUloading?: boolean; + onError?: React.ReactEventHandler<HTMLImageElement>; + /** + * If true, images will be analized and showed according their orientation + * orientation can be landscape if height < width. + * In that case height will be set to 100%. Otherwise width will be set to 100% + */ + smart?: boolean; } export declare type AvatarProps = { @@ -33,5 +42,7 @@ export const defaultAvatarProps: AvatarProps = alt: `avatar`, emptyLabel: "Agregar foto", changeLabel: "Cambiar foto", - readOnly: false + uploadingLabel: "Uploading...", + readOnly: false, + smart: false, } \ No newline at end of file diff --git a/src/files-ui/components/avatar/index.ts b/src/files-ui/components/avatar/index.ts new file mode 100644 index 0000000..81cfdf3 --- /dev/null +++ b/src/files-ui/components/avatar/index.ts @@ -0,0 +1,2 @@ +export { default as Avatar } from "./Avatar"; +export * from "./Avatar"; \ No newline at end of file diff --git a/src/files-ui/components/avatar/useAvatarStyle.ts b/src/files-ui/components/avatar/useAvatarStyle.ts index 4636b14..fd52492 100644 --- a/src/files-ui/components/avatar/useAvatarStyle.ts +++ b/src/files-ui/components/avatar/useAvatarStyle.ts @@ -26,10 +26,23 @@ export const useAvatarStyle = (borderRadius: string | undefined): boolean => { } React.useEffect(() => { - /* if (!borderRadius) { - DynamiCSS.removeStyleSheet(idAvatarStyles); - return; - } */ + return () => { + console.log("avatar, deleting init", styleInjected, idAvatarStyles); + if (styleInjected) { + console.log("avatar, catch css delete"); + + DynamiCSS.removeStyleSheet(idAvatarStyles); + } + setIdAvatarStyles(""); + setStyleInjected(false); + } + }, []); + + React.useEffect(() => { + /* if (!borderRadius) { + DynamiCSS.removeStyleSheet(idAvatarStyles); + return; + } */ let idStyle: string = "avatar-styles"; const styleSheet: DynamicSheet = makeDynamicAvatarCSSRules(borderRadius); // check if classname was added @@ -50,18 +63,10 @@ export const useAvatarStyle = (borderRadius: string | undefined): boolean => { DynamiCSS.editStyleSheet(idAvatarStyles, styleSheet.sheetRules || []); } - return () => { - console.log("avatar, deleting init", styleInjected, idAvatarStyles); - if (styleInjected) { - console.log("avatar, catch css delete"); - DynamiCSS.removeStyleSheet(idAvatarStyles); - } - setIdAvatarStyles(""); - setStyleInjected(false); - } }, [borderRadius]); + /* React.useEffect(() => { return () => { diff --git a/src/files-ui/components/dropzone/components/dropzone/Dropzone.tsx b/src/files-ui/components/dropzone/components/dropzone/Dropzone.tsx index ca99d18..ecdf8d3 100644 --- a/src/files-ui/components/dropzone/components/dropzone/Dropzone.tsx +++ b/src/files-ui/components/dropzone/components/dropzone/Dropzone.tsx @@ -19,7 +19,7 @@ import { UploadResponse, instantPreparingToUploadOne, fakeFuiUpload, - uploadOnePromiseXHR, + uploadExtFile, sleepTransition, toUploadableExtFileList, cleanInput, @@ -272,24 +272,41 @@ const Dropzone: React.FC<DropzoneProps> = (props: DropzoneProps) => { //UPLOADING => UPLOAD() //upload one file and notify about change - const uploadResponse: UploadResponse = fakeUpload - ? await fakeFuiUpload(currentExtFileInstance, DropzoneLocalizer) - : await uploadOnePromiseXHR( - currentExtFileInstance, - url, - method, - headers, - uploadLabel - ); + let uploadResponse: UploadResponse; + try { + uploadResponse = fakeUpload + ? await fakeFuiUpload(currentExtFileInstance, DropzoneLocalizer) + : await uploadExtFile( + currentExtFileInstance, + url, + method, + headers, + uploadLabel + ); + } catch (error) { + uploadResponse = { + id: currentExtFileInstance.id, + serverResponse: { + success: false, + message: "Error on upload: unexpected error " + error, + payload: {}, + }, + uploadedFile: { ...currentExtFileInstance }, + }; + } const { uploadedFile } = uploadResponse; //update instances currentExtFileInstance.uploadStatus = uploadedFile.uploadStatus; currentExtFileInstance.uploadMessage = uploadedFile.uploadMessage; - + //add fake progress only on fakeupload if (fakeUpload) { - console.log("Adding fake progress", fakeUpload, uploadedFile.progress); + console.log( + "Adding fake progress", + fakeUpload, + uploadedFile.progress + ); currentExtFileInstance.progress = uploadedFile.progress; } //CHANGE @@ -526,10 +543,10 @@ const Dropzone: React.FC<DropzoneProps> = (props: DropzoneProps) => { console.log("validatedFuiFileList pre", fuiFileListToValidate); let finalNumberOfValids: number = numberOfValidFiles; - if (behaviour === "replace") { + if (behaviour === "replace") { //re-start number of valids finalNumberOfValids = 0; - } + } const validatedFuiFileList: ExtFile[] = validateExtFileList( fuiFileListToValidate, diff --git a/src/files-ui/components/file-mosaic/components/file-mosaic/FileMosaic.tsx b/src/files-ui/components/file-mosaic/components/file-mosaic/FileMosaic.tsx index e567210..8636799 100644 --- a/src/files-ui/components/file-mosaic/components/file-mosaic/FileMosaic.tsx +++ b/src/files-ui/components/file-mosaic/components/file-mosaic/FileMosaic.tsx @@ -9,7 +9,6 @@ import FileMosaicName from "../FileMosaicName/FileMosaicName"; import FileMosaicUploadLayer from "../FileMosaicUploadLayer/FileMosaicUploadLayer"; import useFileMosaicInitializer from "../../hooks/useFileMosaicInitializer"; import FileMosaicImageLayer from "../FIleMosaicImageLayer/FileMosaicImageLayer"; -import getProgress from "../../hooks/getProgress"; import { useIsUploading } from "../../hooks/useIsUploading"; import { Tooltip } from "../../../tooltip"; import FileMosaicMainLayer from "../FileMosaicMainLayer.tsx/FileMosaicMainLayer"; diff --git a/src/files-ui/components/icons/IconProps/IconProps.ts b/src/files-ui/components/icons/IconProps/IconProps.ts index 56bdb6d..3c6fe21 100644 --- a/src/files-ui/components/icons/IconProps/IconProps.ts +++ b/src/files-ui/components/icons/IconProps/IconProps.ts @@ -1,7 +1,7 @@ import { CSSProperties } from "react"; export interface IconProps { - size?: "micro" | "small" | "semi-medium" | "medium" | "large" | number; + size?: "micro" | "small" | "semi-medium" | "medium" | "large"| "extra-large" | number; /** * main color for icon */ diff --git a/src/files-ui/components/icons/utils/utils.ts b/src/files-ui/components/icons/utils/utils.ts index 0676af4..0559dca 100644 --- a/src/files-ui/components/icons/utils/utils.ts +++ b/src/files-ui/components/icons/utils/utils.ts @@ -19,6 +19,8 @@ export const parseSize = (sizeStr: IconProps["size"] | number): number => { return 25; case "large": return 28; + case "extra-large": + return 32; default: return 24; } diff --git a/src/files-ui/components/index.ts b/src/files-ui/components/index.ts index fad03ef..010e67f 100644 --- a/src/files-ui/components/index.ts +++ b/src/files-ui/components/index.ts @@ -1,3 +1,6 @@ +export { Avatar } from "./avatar"; +export * from "./avatar"; + export { Dropzone } from "./dropzone"; export * from "./dropzone"; diff --git a/src/files-ui/components/loader/LoaderContainer/LoaderContainerProps.ts b/src/files-ui/components/loader/LoaderContainer/LoaderContainerProps.ts index 22c71e0..20ef4fb 100644 --- a/src/files-ui/components/loader/LoaderContainer/LoaderContainerProps.ts +++ b/src/files-ui/components/loader/LoaderContainer/LoaderContainerProps.ts @@ -1,7 +1,7 @@ import { OverridableComponentProps } from "../../overridable"; export interface LoaderContainerPropsMap extends OverridableComponentProps { - size?: "micro" | "small" | "semi-medium" | "medium" | "large" | number; + size?: "micro" | "small" | "semi-medium" | "medium" | "large"| "extra-large" | number; onClick?: Function; text?:string; } diff --git a/src/files-ui/components/previews/FullScreen/FullScreen.scss b/src/files-ui/components/previews/FullScreen/FullScreen.scss index 584d77b..727ca50 100644 --- a/src/files-ui/components/previews/FullScreen/FullScreen.scss +++ b/src/files-ui/components/previews/FullScreen/FullScreen.scss @@ -4,7 +4,7 @@ align-items: center; justify-content: center; width: 100%; - height: 100%; + height: 100vh; top: 0; left: 0; background: rgba(0, 0, 0, 0.734); @@ -15,29 +15,46 @@ transform: translate(0); } z-index: 4; + box-sizing: border-box; + //padding: 10px 50px; } .fui-fullscreen-relative-container { position: relative; - //overflow: hidden; - height: 80%; - //width: 60%; - width: 80%; + width: 90%; + height: 90%; + overflow: hidden; display: flex; align-items: center; justify-content: center; + img{ + height: 100%; + width: auto; + } + video { + height: 100%; + width: auto; + } /* @media (max-width: 600px) { width: 80%; height: auto; - } + }*/ @media (max-width: 960px) { - width: 90%; - height: auto; - } */ + height: 90%; + width: 100%; + video { + height: auto; + width: 100%; + } + img{ + height: auto; + width: 100%; + } + } } .button-full-screen { + position: absolute; top: 0; right: 0; - position: absolute; } diff --git a/src/files-ui/components/previews/FullScreen/FullScreen.tsx b/src/files-ui/components/previews/FullScreen/FullScreen.tsx index 67d259f..4cc9b0f 100644 --- a/src/files-ui/components/previews/FullScreen/FullScreen.tsx +++ b/src/files-ui/components/previews/FullScreen/FullScreen.tsx @@ -1,9 +1,8 @@ import * as React from "react"; -import { Cancel } from "../../icons"; +import { Clear } from "../../icons"; import { FullScreenProps } from "./FullScreenProps"; import "./FullScreen.scss"; const FullScreen: React.FC<FullScreenProps> = (props: FullScreenProps) => { - const { open, onClose, children } = props; function handleClose<T extends HTMLDivElement>( @@ -24,21 +23,20 @@ const FullScreen: React.FC<FullScreenProps> = (props: FullScreenProps) => { {open && ( <div className="fui-fullscreen-relative-container" - onClick={(evt) => { - evt.preventDefault(); - }} + onClick={handleClose} > {children} - {onClose && ( - <Cancel - color="rgba(255,255,255,0.8)" - onClick={handleClose} - colorFill="black" - className="button-full-screen" - /> - )} </div> )} + {onClose && ( + <Clear + color="rgba(255,255,255,0.8)" + onClick={handleClose} + colorFill="transparent" + className="button-full-screen" + size={"extra-large"} + /> + )} </div> ); }; diff --git a/src/files-ui/components/previews/ImagePreview/ImagePreview.tsx b/src/files-ui/components/previews/ImagePreview/ImagePreview.tsx index 354aef6..a7761da 100644 --- a/src/files-ui/components/previews/ImagePreview/ImagePreview.tsx +++ b/src/files-ui/components/previews/ImagePreview/ImagePreview.tsx @@ -11,10 +11,8 @@ import "./ImagePreview.scss"; const ImagePreview: React.FC<ImagePreviewProps> = ( props: ImagePreviewProps ) => { - const { src, alt, className, style, width, height, onError } = mergeProps( - props, - ImagePreviewDefaultProps - ); + const { src, alt, className, style, width, height, onError, smart } = + mergeProps(props, ImagePreviewDefaultProps); //console.table({ src, alt, className, style, width, height }); const [source, setSource] = React.useState<string | undefined>(undefined); @@ -26,15 +24,22 @@ const ImagePreview: React.FC<ImagePreviewProps> = ( const newImageSrc: string | undefined = await readAsDataURL(src); handleSetStrSource(newImageSrc); }; + const handleSetStrSource = async (imageSource: string | undefined) => { - if (imageSource) { - const orientation: "landscape" | "portrait" = await getImageOrientation( - imageSource - ); - setOrientation(orientation); - } + if (imageSource === "" || !imageSource) return; + + setSource(imageSource); + + if (!smart) return; + + const orientation: "landscape" | "portrait" = await getImageOrientation( + imageSource + ); + setOrientation(orientation); + setSource(imageSource); }; + React.useEffect(() => { //if not undefined if (!src) { @@ -58,14 +63,16 @@ const ImagePreview: React.FC<ImagePreviewProps> = ( //console.log("ImagePreview", src, source); const finalWidth: string | number | undefined = - width || (orientation === "landscape" ? "100%" : undefined); + width || (orientation === "landscape" && smart ? "100%" : undefined); const finalHeight: string | number | undefined = - height || (orientation === "portrait" ? "100%" : undefined); - - const handleError=(evt: React.SyntheticEvent<HTMLImageElement, Event>)=>{ - console.log("handleError", onError); - onError?.(evt); - } + height || (orientation === "portrait" && smart ? "100%" : undefined); + + console.log("Image result", finalHeight, finalWidth, orientation, smart); + const handleError = (evt: React.SyntheticEvent<HTMLImageElement, Event>) => { + console.log("handleError", onError); + onError?.(evt); + }; + return ( <React.Fragment> {src && source && ( diff --git a/src/files-ui/components/previews/ImagePreview/ImagePreviewProps.ts b/src/files-ui/components/previews/ImagePreview/ImagePreviewProps.ts index dbcc924..f008f69 100644 --- a/src/files-ui/components/previews/ImagePreview/ImagePreviewProps.ts +++ b/src/files-ui/components/previews/ImagePreview/ImagePreviewProps.ts @@ -25,6 +25,12 @@ export interface ImagePreviewProps extends OverridableComponentProps { * Fallback when the image is not loaded correctly */ onError?: React.ReactEventHandler<HTMLImageElement> | undefined; + /** + * If true, images will be analized and showed according their orientation + * orientation can be landscape if height < width. + * In that case height will be set to 100%. Otherwise width will be set to 100% + */ + smart?: boolean; } export const ImagePreviewDefaultProps: ImagePreviewProps = { @@ -32,4 +38,5 @@ export const ImagePreviewDefaultProps: ImagePreviewProps = { //height: "100%", alt: "image-preview", //className: "fui-image-preview" + smart:true } \ No newline at end of file diff --git a/src/files-ui/core/index.ts b/src/files-ui/core/index.ts index b4651ce..aea53dc 100644 --- a/src/files-ui/core/index.ts +++ b/src/files-ui/core/index.ts @@ -96,7 +96,7 @@ export { } from "./types" export { - FilesUIUpload, uploadPromiseXHR, + uploadExtFile, FuiUpload, completeUploadResult, instantPreparingToUploadOne, @@ -104,8 +104,20 @@ export { sleepTransition, toUploadableExtFileList, unableToUploadResult, - unexpectedErrorUploadResult, uploadOnePromiseXHR, + makeServerResponse, + uploadFile, + uploadFormData, + ABORTED_ERROR_RESPONSE, + JSON_PARSE_ERROR_RESPONSE, + JsonParseResponse, + NO_XHR_PROVIDED_ERROR, + TIMEOUT_ERROR_RESPONSE, + UNEXPECTED_ERROR_RESPONSE, + makeErrorUploadResponse, + makeSuccessUploadResponse, + addExtraData, + addHeaders } from "./upload"; export { @@ -139,7 +151,7 @@ export { } from "./validation"; -export { createFuiRippleFromDiv,createRippleButton } from "./ripple"; +export { createFuiRippleFromDiv, createRippleButton } from "./ripple"; export { asureColor, diff --git a/src/files-ui/core/upload/addExtraData.upload.ts b/src/files-ui/core/upload/addExtraData.upload.ts new file mode 100644 index 0000000..40c5135 --- /dev/null +++ b/src/files-ui/core/upload/addExtraData.upload.ts @@ -0,0 +1,17 @@ +export default function ( + formData: FormData, + extraData: Record<string, string> | undefined +) { + //headers + const extraDataKeys: string[] = Object.keys(extraData || {}); + //const headerValues: string[] = Object.values(headers); + for (let i = 0; i < extraDataKeys.length && extraData; i++) { + console.log("uploadFile extraData", extraDataKeys[i], extraData[extraDataKeys[i]]); + + formData.append(extraDataKeys[i], extraData[extraDataKeys[i]]); + + } + + formData.append("otherValue", "HAAAAAAAAAAAAAAa"); + +} \ No newline at end of file diff --git a/src/files-ui/core/upload/addheaders.upload.ts b/src/files-ui/core/upload/addheaders.upload.ts new file mode 100644 index 0000000..2ba05b7 --- /dev/null +++ b/src/files-ui/core/upload/addheaders.upload.ts @@ -0,0 +1,17 @@ +export default function addHeaders( + xhr: XMLHttpRequest, + headers: Record<string, string> | undefined +) { + //headers + const headerKeys: string[] = Object.keys(headers || {}); + //const headerValues: string[] = Object.values(headers); + for (let i = 0; i < headerKeys.length && headers; i++) { + console.log("uploadFile headers", headerKeys[i], headers[headerKeys[i]]); + xhr.setRequestHeader( + headerKeys[i], + headers[headerKeys[i]] + ); + } + + +} \ No newline at end of file diff --git a/src/files-ui/core/upload/errors.upload.ts b/src/files-ui/core/upload/errors.upload.ts new file mode 100644 index 0000000..d4b3b00 --- /dev/null +++ b/src/files-ui/core/upload/errors.upload.ts @@ -0,0 +1,37 @@ +import { ExtFile, UploadResponse } from "../types"; + +export const TIMEOUT_ERROR_RESPONSE = { + success: false, + message: "Timeout error", + payload: {} +}; +export const ABORTED_ERROR_RESPONSE = { + success: false, + message: "Upload aborted", + payload: {} +} +export const JSON_PARSE_ERROR_RESPONSE = { + success: false, + message: "Error when parsing JSON response", + payload: {} +} + +export const UNEXPECTED_ERROR_RESPONSE = { + success: false, + message: "Unexpected error", + payload: {} +} + +export const NO_XHR_PROVIDED_ERROR = (extFile: ExtFile): UploadResponse => { + return { + uploadedFile: + { + ...extFile, + uploadMessage: "Unable to upload. xhr object was not provided", + uploadStatus: "error" + }, + + id: extFile.id, + serverResponse: {} + } +} \ No newline at end of file diff --git a/src/files-ui/core/upload/index.ts b/src/files-ui/core/upload/index.ts index 515c087..9977205 100644 --- a/src/files-ui/core/upload/index.ts +++ b/src/files-ui/core/upload/index.ts @@ -1,15 +1,36 @@ export { - FilesUIUpload, uploadPromiseXHR, + + uploadExtFile, FuiUpload, completeUploadResult, + unableToUploadResult, + uploadOnePromiseXHR, + uploadFile, + uploadFormData, +} from "./upload"; +export { + ABORTED_ERROR_RESPONSE, + JSON_PARSE_ERROR_RESPONSE, + NO_XHR_PROVIDED_ERROR, + TIMEOUT_ERROR_RESPONSE, + UNEXPECTED_ERROR_RESPONSE, +} from "./errors.upload"; +export { + JsonParseResponse, + makeErrorUploadResponse, + makeServerResponse, + makeSuccessUploadResponse, +} from "./response.upload"; + +export { default as addExtraData } from "./addExtraData.upload"; +export { default as addHeaders } from "./addheaders.upload"; + +export { instantPreparingToUploadOne, preparingToUploadOne, sleepTransition, toUploadableExtFileList, - unableToUploadResult, - unexpectedErrorUploadResult, - uploadOnePromiseXHR, -} from "./upload"; +} from "./utils.upload"; /* export { FuiUpload, completeUploadResult, diff --git a/src/files-ui/core/upload/response.upload.ts b/src/files-ui/core/upload/response.upload.ts new file mode 100644 index 0000000..047b24a --- /dev/null +++ b/src/files-ui/core/upload/response.upload.ts @@ -0,0 +1,61 @@ +import { ExtFile, ServerResponse, UploadResponse } from "../types"; +import { JSON_PARSE_ERROR_RESPONSE } from "./errors.upload"; + +export const makeServerResponse = (success: any, message: string, payload: any): ServerResponse => { + const result: ServerResponse = { success: success, message: message, payload: payload } as ServerResponse; + return result; +} +export const JsonParseResponse = (xhr: XMLHttpRequest): ServerResponse => { + try { + const jsonResponse = JSON.parse(xhr.response); + const success: any = jsonResponse.success; + const message: string = jsonResponse.message; + const payload: any = jsonResponse.payload; + + const fuiResponse: ServerResponse = { + success: typeof success === "boolean" ? success : false, + message: typeof message === "string" ? message : "Error on message response", + payload: payload || {} + } + return fuiResponse + } catch (error) { + console.log("FuiUpload ERROR", error); + return JSON_PARSE_ERROR_RESPONSE; + } +} + + + +export const makeSuccessUploadResponse = ( + extFile: ExtFile, + responseFui: ServerResponse +): UploadResponse => { + return { + id: extFile.id, + serverResponse: responseFui, + uploadedFile: + { + ...extFile, + uploadMessage: responseFui.message, + uploadStatus: "success" + }, + } +} + + +export const makeErrorUploadResponse = ( + extFile: ExtFile, + responseFui: ServerResponse +): UploadResponse => { + return { + id: extFile.id, + serverResponse: responseFui, + uploadedFile: + { + ...extFile, + uploadMessage: responseFui.message, + uploadStatus: "error" + }, + } +} + diff --git a/src/files-ui/core/upload/upload.ts b/src/files-ui/core/upload/upload.ts index 9d619bb..8a3c743 100644 --- a/src/files-ui/core/upload/upload.ts +++ b/src/files-ui/core/upload/upload.ts @@ -1,106 +1,11 @@ import { ExtFile, ExtFileInstance, Method, UPLOADSTATUS } from "../types"; import { ServerResponse, UploadResponse } from "../types/uploadTypes"; -export const makeServerResponse = (success: boolean, message: string, payload: any): ServerResponse => { - const result: ServerResponse = { success: success, message: message, payload: payload } as ServerResponse; - return result; -} -export function uploadFile( - file: File, - url: string, - method?: Method, - label?: string, - headers?: Record<string, string> -): Promise<ServerResponse> { - return new Promise((resolve, reject) => { - let uploadResult: ServerResponse = makeServerResponse(false, "", {}); - const finalMethod: string = method && ["POST", "PUT", "PATCH"].includes(method.toLocaleLowerCase()) ? method : "POST"; - - //XMLHttpRequest Object - const xhr: XMLHttpRequest = new XMLHttpRequest(); - xhr.upload.onload = () => { - console.log("uploadFile onLoad", xhr.readyState, xhr.response); - }; - xhr.upload.ontimeout = () => { - uploadResult = makeServerResponse(false, "Timeout error", {}); - resolve(uploadResult); - }; - xhr.upload.onabort = () => { - uploadResult = makeServerResponse(false, "Upload aborted", {}); - resolve(uploadResult); - }; - xhr.onreadystatechange = async (e) => { - console.log("uploadFile onreadystatechange", xhr.readyState, xhr.response); - if (xhr.readyState === 4 && xhr.response !== "") { - let fuiServerRes: ServerResponse; - try { - const jsonResponse = JSON.parse(xhr.response); - const success: boolean = jsonResponse.success; - const message: string = jsonResponse.message; - const payload: any = jsonResponse.payload; - - console.log("uploadFile ====> status", success); - console.log("uploadFile ====> message", message); - console.log("uploadFile ====> payload", payload); - - fuiServerRes = { - success: typeof success === "boolean" ? success : false, - message: typeof message === "string" ? message : "Error on response", - payload: payload || {} - } - resolve(fuiServerRes); - } catch (error) { - fuiServerRes = { - success: false, - message: "Unexpected error: " + error, - payload: {} - } - console.log("uploadFile ERROR", error); - resolve(fuiServerRes); - } - } else { - console.log("uploadFile Naranjas Changed: ", xhr.readyState, xhr.response); - } - - } - // open request - - xhr.open(finalMethod, url, true); - - //headers - const headerKeys: string[] = Object.keys(headers || {}); - //const headerValues: string[] = Object.values(headers); - for (let i = 0; i < headerKeys.length && headers; i++) { - console.log("uploadFile headers", headerKeys[i], headers[headerKeys[i]]); - xhr.setRequestHeader( - headerKeys[i], - headers[headerKeys[i]] - ); - } - - //start uploading - const formData = new FormData(); - formData.append(label || "file", file); - xhr.send(formData); - }); - - -} -export function uploadFormData( - file: File, - url: string, - method?: Method, - label?: string, - headers?: Record<string, string> -): Promise<ServerResponse> { - return new Promise((resolve, reject) => { - let uploadResult: ServerResponse = { success: false, message: "", payload: {} }; +import addExtraDataUpload from "./addExtraData.upload"; +import addHeaders from "./addheaders.upload"; +import { ABORTED_ERROR_RESPONSE, NO_XHR_PROVIDED_ERROR, TIMEOUT_ERROR_RESPONSE, UNEXPECTED_ERROR_RESPONSE } from "./errors.upload"; +import { JsonParseResponse, makeErrorUploadResponse, makeServerResponse, makeSuccessUploadResponse } from "./response.upload"; - - resolve(uploadResult); - }); -} - /** * Uploads one formData object to a given endpoint in a promisified way * @param xhr XMLHTTPrequest object @@ -110,85 +15,45 @@ export function uploadFormData( * @param headers the set of headers * @returns a server response that consists on {status, payload, message} */ -export const FilesUIUpload = ( +export const uploadFormData = ( xhr: XMLHttpRequest, - method: Method, + method: Method | undefined = "POST", endpoint: string, data: FormData, - headers: Record<string, string> + headers: Record<string, string> | undefined ) => { return new Promise<ServerResponse>((resolve, reject) => { console.log("uploadFile", xhr, method, endpoint, data, headers); + const finalMethod: Method = ["POST", "PUT", "PATCH"].includes(method.toUpperCase()) ? method : "POST"; + xhr.upload.onload = () => { console.log("uploadFile onLoad", xhr.readyState, xhr.response); }; - xhr.upload.ontimeout = () => { - //onError("Timeout error"); - resolve( - { - success: false, - message: "Timeout error", - payload: {} - } - ); - }; - - xhr.upload.onabort = () => { - resolve( - { - success: false, - message: "Upload aborted", - payload: {} - } - ); - }; + xhr.upload.ontimeout = () => resolve(TIMEOUT_ERROR_RESPONSE); + xhr.upload.onabort = () => resolve(ABORTED_ERROR_RESPONSE); // listen for `progress` event //currently listening on FileItem component hook xhr.onreadystatechange = async (e) => { //console.log("Finished", xhr); console.log("uploadFile onreadystatechange", xhr.readyState, xhr.response); - if (xhr.readyState === 4 && xhr.response !== "") { - let fuiServerRes: ServerResponse; - try { - const jsonResponse = JSON.parse(xhr.response); - const success: any = jsonResponse.success; - const message: string = jsonResponse.message; - const payload: any = jsonResponse.payload; - console.log("uploadFile ====> status", success); - console.log("uploadFile ====> message", message); - console.log("uploadFile ====> payload", payload); - - fuiServerRes = { - success: typeof success === "boolean" ? success : false, - message: typeof message === "string" ? message : "Error on response", - payload: payload || {} - } - resolve(fuiServerRes); - } catch (error) { - fuiServerRes = { - success: false, - message: "Unexpected error", - payload: {} - } - console.log("uploadFile ERROR", error); - resolve(fuiServerRes); + if (xhr.readyState === 4) { + if (xhr.response !== "") { + //there is th answer + resolve(JsonParseResponse(xhr)); + } else { + //error unexpected + resolve(UNEXPECTED_ERROR_RESPONSE); } } else { - console.log("uploadFile Naranjas Changed to " + xhr.readyState); + console.log("FuiUpload NOT YET" + xhr.readyState); } }; // open request - xhr.open(method, endpoint, true); - const headerKeys: string[] = Object.keys(headers); - //const headerValues: string[] = Object.values(headers); - for (let i = 0; i < headerKeys.length; i++) { - console.log("uploadFile FuiUpload headers", headerKeys[i], headers[headerKeys[i]]); - xhr.setRequestHeader( - headerKeys[i], - headers[headerKeys[i]] - ); - } + xhr.open(finalMethod, endpoint, true); + + //add header to request + addHeaders(xhr, headers); //start uploading xhr.send(data); }); @@ -203,128 +68,90 @@ export const FilesUIUpload = ( * @param headers headers for request * @returns */ -export const uploadPromiseXHR = async ( - file: ExtFile, +export const uploadExtFile = async ( + extFile: ExtFile, url: string, - method: Method, - headers?: Record<string, string> + method?: Method, + headers?: Record<string, string>, + uploadLabel?: string, ): Promise<UploadResponse> => { return new Promise(async (resolve, reject) => { try { - const uploader: XMLHttpRequest | undefined = file.xhr; + const uploader: XMLHttpRequest | undefined = extFile.xhr; + if (!uploader) { - resolve( - { - uploadedFile: - { - ...file, - uploadMessage: "Unable to upload. xhr object was not provided", - uploadStatus: "error" - }, - - id: file.id, - serverResponse: {} - - } - ); + resolve(NO_XHR_PROVIDED_ERROR(extFile)); return; } const localMethod: Method = method || "POST"; - - const fileToUpload: File = file.file as File; + const fileToUpload: File = extFile.file as File; const formData = new FormData(); - formData.append("file", fileToUpload); - formData.append("otherValue", "HAAAAAAAAAAAAAAa"); + formData.append(uploadLabel || "file", fileToUpload); + // add extra data to upload + const finalExtraData: Record<string, any> = + { otherValue: "other valueee haaaa", param2: { tecnica: "KIKOHUUUU", friend: "Chaos", age: 25 }, ...extFile.extraUploadData }; + + addExtraDataUpload(formData, finalExtraData); console.log("FORMDATA", formData); + let responseFui: ServerResponse; - //stablish events - responseFui = await FilesUIUpload( + responseFui = await uploadFormData( uploader, localMethod, url, formData, headers || {}); - if (responseFui.success) { // status is true - resolve( - { - id: file.id, - serverResponse: responseFui, - uploadedFile: - { - ...file, - uploadMessage: responseFui.message, - uploadStatus: "success" - }, - - - } - - ); + resolve(makeSuccessUploadResponse(extFile, responseFui)); } else { // status is false - resolve( - { - id: file.id, - serverResponse: responseFui, - uploadedFile: - { - ...file, - uploadMessage: responseFui.message, - uploadStatus: "error" - }, - - - } - ); + resolve(makeErrorUploadResponse(extFile, responseFui)); } } catch (error) { // on error console.log("uploadPromiseXHR uploadPromiseXHR ERROR", error); resolve( - { - id: file.id, - serverResponse: {}, - uploadedFile: - { - ...file, - uploadMessage: "Unexpected error", - uploadStatus: "error" - }, - - - - } + makeErrorUploadResponse(extFile, UNEXPECTED_ERROR_RESPONSE) ); } }); }; -/// refactorizar, entregar solamente -///input: file o formData, url, method, headers y label on backend -// {payload, success, message:str} -export const unexpectedErrorUploadResult = (extFile: ExtFile): UploadResponse => { - return { - id: extFile.id, - uploadedFile: - { - ...extFile, - uploadMessage: "Unable to upload. xhr object was not provided", - uploadStatus: "error" - }, - serverResponse: { +export function uploadFile( + file: File, + url: string, + method?: Method, + label?: string, + headers?: Record<string, string> +): Promise<ServerResponse> { + return new Promise(async (resolve, reject) => { + + + //start uploading + const formData = new FormData(); + + formData.append(label || "file", file); + + try { + const serverResponse: ServerResponse = await uploadFormData(new XMLHttpRequest(), method, url, formData, headers); + resolve(serverResponse); + } catch (error) { + // on error + console.log("uploadPromiseXHR uploadPromiseXHR ERROR", error); + resolve(UNEXPECTED_ERROR_RESPONSE); } - } + }); } + export const unableToUploadResult = ( extFile: ExtFile ): UploadResponse => { @@ -339,6 +166,7 @@ export const unableToUploadResult = ( } } } + export const completeUploadResult = ( extFile: ExtFile, serverResponse: ServerResponse, @@ -354,6 +182,10 @@ export const completeUploadResult = ( serverResponse: serverResponse } } + +/** + * @deprecated + */ export const uploadOnePromiseXHR = async ( extFile: ExtFile, url: string, @@ -383,7 +215,9 @@ export const uploadOnePromiseXHR = async ( } else formData.append("file", fileToUpload); - const finalExtraData: Record<string, any> = { otherValue: "other valueee haaaa", param2: { tecnica: "KIKOHUUUU", friend: "Chaos", age: 25 } }; + const finalExtraData: Record<string, any> = + { otherValue: "other valueee haaaa", param2: { tecnica: "KIKOHUUUU", friend: "Chaos", age: 25 }, ...extraData }; + if (finalExtraData) { const extraDataKeys: string[] = Object.keys(finalExtraData); @@ -414,7 +248,9 @@ export const uploadOnePromiseXHR = async ( } }); }; + /** + * @deprecated * Uploads one formData object to a given endpoint in a promisified way * @param xhr XMLHTTPrequest object * @param method method for uploading @@ -435,112 +271,29 @@ export const FuiUpload = ( xhr.upload.onload = () => { console.log("FuiUpload onLoad", xhr.readyState, xhr.response); - }; - xhr.upload.ontimeout = () => { - //onError("Timeout error"); - resolve( - { - success: false, - message: "Timeout error", - payload: {} - } - ); - }; + xhr.upload.ontimeout = () => resolve(TIMEOUT_ERROR_RESPONSE); + xhr.upload.onabort = () => resolve(ABORTED_ERROR_RESPONSE); - xhr.upload.onabort = () => { - resolve( - { - success: false, - message: "Upload aborted", - payload: {} - } - ); - }; // listen for `progress` event - //currently listening on FileItem component hook + //currently listening on FileMosaic component hook + xhr.onreadystatechange = async (e) => { //console.log("Finished", xhr); console.log("FuiUpload onreadystatechange", xhr.readyState, xhr.response, xhr); - let duiRes = { - success: false, - message: "Unexpected error", - payload: {} - } + if (xhr.readyState === 4) { if (xhr.response !== "") { //there is th answer - let duiRes: ServerResponse; - try { - const jsonResponse = JSON.parse(xhr.response); - const success: any = jsonResponse.success; - const message: string = jsonResponse.message; - const payload: any = jsonResponse.payload; - - duiRes = { - success: typeof success === "boolean" ? success : false, - message: typeof message === "string" ? message : "Error on message response", - payload: payload || {} - } - resolve(duiRes); - } catch (error) { - duiRes = { - success: false, - message: "Error when parsing JSON response", - payload: {} - } - console.log("FuiUpload ERROR", error); - resolve(duiRes); - } + resolve(JsonParseResponse(xhr)); } else { //error unexpected - duiRes = { - success: false, - message: "Unexpected error", - payload: {} - } - resolve(duiRes); + resolve(UNEXPECTED_ERROR_RESPONSE); } } else { console.log("FuiUpload NOT YET" + xhr.readyState); } - - /* if (xhr.readyState === 4 && xhr.response !== "") { - let duiRes: ServerResponse; - try { - const jsonResponse = JSON.parse(xhr.response); - const success: any = jsonResponse.success; - const message: string = jsonResponse.message; - const payload: any = jsonResponse.payload; - console.log("FuiUpload ====> success", success); - console.log("FuiUpload ====> message", message); - console.log("FuiUpload====> payload", payload); - - duiRes = { - success: typeof success === "boolean" ? success : false, - message: typeof message === "string" ? message : "Error on response", - payload: payload || {} - } - resolve(duiRes); - } catch (error) { - duiRes = { - success: false, - message: "Unexpected error", - payload: {} - } - console.log("FuiUpload ERROR", error); - resolve(duiRes); - } - } else { - console.log("FuiUpload Naranjas Changed to " + xhr.readyState); - const duiRes = { - success: false, - message: "Unexpected error", - payload: {} - } - resolve(duiRes); - } */ }; // open request xhr.open(method, endpoint, true); @@ -558,75 +311,5 @@ export const FuiUpload = ( }); }; -/** - * Initializes the xhr attribute for performing uploads - * @param extFileList the list of extended files - * @returns the array of extFiles with the xhr attribute initialized - */ -export const toUploadableExtFileList = ( - extFileList: ExtFile[] | ExtFileInstance[] -): ExtFile[] => { - if (!extFileList) return []; - return extFileList.map(extFile => { - return { ...extFile, xhr: new XMLHttpRequest() } - }); -} -/** - * Updates the uploadStatus of the given extFile - * from "preparing" to "uploading" - * @param extFile the extended file - * @returns the extended file with uploadStatus updated to "uploading" - */ -export const instantPreparingToUploadOne = ( - extFile: ExtFileInstance | ExtFile -): ExtFileInstance | ExtFile => { - if (extFile.uploadStatus === "preparing") { - //for ExtFile instance - extFile.uploadStatus = "uploading"; - //for ExtFile type - return { - ...extFile, - uploadStatus: "uploading", - }; - } - return extFile; -}; - -/** - * - * @param extFile the extended file - * @returns - */ -export const preparingToUploadOne = ( - extFile: ExtFileInstance | ExtFile -): Promise<ExtFileInstance | ExtFile> => { - return new Promise((resolve, reject) => { - setTimeout(() => { - if (extFile.uploadStatus === "preparing") { - //for ExtFile instance - extFile.uploadStatus = "uploading"; - //for ExtFile type - resolve({ - ...extFile, - uploadStatus: "uploading", - }); - } else - resolve(extFile); - }, 1500); - }); -}; -/** - * Sleeps for 1200 miliseconds for showing a better transition - * on uploading - * @returns true is everything is ok - */ -export const sleepTransition = ( -): Promise<boolean> => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve(true); - }, 1200); - }); -} diff --git a/src/files-ui/core/upload/upload.utils.ts b/src/files-ui/core/upload/upload.utils.ts deleted file mode 100644 index 2a64806..0000000 --- a/src/files-ui/core/upload/upload.utils.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { ExtFile, ExtFileInstance, Method, ServerResponse, UploadResponse, UPLOADSTATUS } from "../types" - -export const unexpectedErrorUploadResult = (extFile: ExtFile): UploadResponse => { - return { - id: extFile.id, - uploadedFile: - { - ...extFile, - uploadMessage: "Unable to upload. xhr object was not provided", - uploadStatus: "error" - }, - serverResponse: { - } - } -} -export const unableToUploadResult = ( - extFile: ExtFile -): UploadResponse => { - return { - id: extFile.id, - uploadedFile: { - ...extFile, - uploadMessage: "Unable to upload. XHR was not provided", - uploadStatus: "error" - }, - serverResponse: { - } - } -} -export const completeUploadResult = ( - extFile: ExtFile, - serverResponse: ServerResponse, - result: UPLOADSTATUS -): UploadResponse => { - return { - id: extFile.id, - uploadedFile: { - ...extFile, - uploadMessage: serverResponse.message, - uploadStatus: result - }, - serverResponse: serverResponse - } -} -export const uploadOnePromiseXHR = async ( - extFile: ExtFile, - url: string, - method?: Method, - headers?: Record<string, string>, - uploadLabel?: string -): Promise<UploadResponse> => { - return new Promise(async (resolve, reject) => { - try { - const uploader: XMLHttpRequest | undefined = extFile.xhr; - if (!uploader) { - const duiUploadResponse: UploadResponse = unableToUploadResult(extFile); - resolve(duiUploadResponse); - return; - } - const localMethod: Method = (method) || "POST"; - const fileToUpload: File = extFile.file as File; - - const formData = new FormData(); - if (typeof uploadLabel === "string" && uploadLabel.length > 0) - formData.append(uploadLabel, fileToUpload); - else - formData.append("file", fileToUpload); - - let serverResponse: ServerResponse; - //stablish events - serverResponse = await FuiUpload(uploader, localMethod, url, formData, headers || {}); - - if (serverResponse.success) { - const duiUploadResponse: UploadResponse = completeUploadResult(extFile, serverResponse, "success"); - resolve(duiUploadResponse); - } else { - // success is false - const duiUploadResponse: UploadResponse = completeUploadResult(extFile, serverResponse, "error"); - resolve(duiUploadResponse); - } - } catch (error) { - // on error - console.log("ERROR", error); - const duiUploadResponse: UploadResponse = unableToUploadResult(extFile); - resolve(duiUploadResponse); - } - }); -}; -/** - * Uploads one formData object to a given endpoint in a promisified way - * @param xhr XMLHTTPrequest object - * @param method method for uploading - * @param endpoint endpoint to upload the file - * @param data FromData object to perform multipart form data - * @param headers the set of headers - * @returns a dui server response that consists on {success, payload, message} - */ -export const FuiUpload = ( - xhr: XMLHttpRequest, - method: Method, - endpoint: string, - data: FormData, - headers: Record<string, string> -) => { - return new Promise<ServerResponse>((resolve, reject) => { - console.log("DuiUpload", xhr, method, endpoint, data, headers); - xhr.upload.onload = () => { - console.log("DuiUpload onLoad", xhr.readyState, xhr.response); - - }; - - xhr.upload.ontimeout = () => { - //onError("Timeout error"); - resolve( - { - success: false, - message: "Timeout error", - payload: {} - } - ); - }; - - xhr.upload.onabort = () => { - resolve( - { - success: false, - message: "Upload aborted", - payload: {} - } - ); - }; - // listen for `progress` event - //currently listening on FileItem component hook - xhr.onreadystatechange = async (e) => { - //console.log("Finished", xhr); - console.log("DuiUpload onreadystatechange", xhr.readyState, xhr.response); - if (xhr.readyState === 4 && xhr.response !== "") { - let duiRes: ServerResponse; - try { - const jsonResponse = JSON.parse(xhr.response); - const success: any = jsonResponse.success; - const message: string = jsonResponse.message; - const payload: any = jsonResponse.payload; - console.log("====> success", success); - console.log("====> message", message); - console.log("====> payload", payload); - - duiRes = { - success: typeof success === "boolean" ? success : false, - message: typeof message === "string" ? message : "Error on response", - payload: payload || {} - } - resolve(duiRes); - } catch (error) { - duiRes = { - success: false, - message: "Unexpected error", - payload: {} - } - console.log("DuiUpload ERROR", error); - resolve(duiRes); - } - } else { - console.log("Naranjas Changed to " + xhr.readyState); - } - }; - // open request - xhr.open(method, endpoint, true); - const headerKeys: string[] = Object.keys(headers); - //const headerValues: string[] = Object.values(headers); - for (let i = 0; i < headerKeys.length; i++) { - console.log("DuiUpload headers", headerKeys[i], headers[headerKeys[i]]); - xhr.setRequestHeader( - headerKeys[i], - headers[headerKeys[i]] - ); - } - //start uploading - xhr.send(data); - }); - -}; -/** - * Initializes the xhr attribute for performing uploads - * @param extFileList the list of extended files - * @returns the array of extFiles with the xhr attribute initialized - */ -export const toUploadableExtFileList = ( - extFileList: ExtFile[] | ExtFileInstance[] - ): ExtFile[] => { - if (!extFileList) return []; - return extFileList.map(extFile => { - return { ...extFile, xhr: new XMLHttpRequest() } - }); -} - -/** - * Updates the uploadStatus of the given extFile - * from "preparing" to "uploading" - * @param extFile the extended file - * @returns the extended file with uploadStatus updated to "uploading" - */ -export const instantPreparingToUploadOne = ( - extFile: ExtFileInstance | ExtFile -): ExtFileInstance | ExtFile => { - if (extFile.uploadStatus === "preparing") { - //for ExtFile instance - extFile.uploadStatus = "uploading"; - //for ExtFile type - return { - ...extFile, - uploadStatus: "uploading", - }; - } - return extFile; -}; - -/** - * - * @param extFile the extended file - * @returns - */ -export const preparingToUploadOne = ( - extFile: ExtFileInstance | ExtFile -): Promise<ExtFileInstance | ExtFile> => { - return new Promise((resolve, reject) => { - setTimeout(() => { - if (extFile.uploadStatus === "preparing") { - //for ExtFile instance - extFile.uploadStatus = "uploading"; - //for ExtFile type - resolve({ - ...extFile, - uploadStatus: "uploading", - }); - } else - resolve(extFile); - }, 1500); - }); -}; -/** - * Sleeps for 1200 miliseconds for showing a better transition - * on uploading - * @returns true is everything is ok - */ -export const sleepTransition = ( -): Promise<boolean> => { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve(true); - }, 1200); - }); -} \ No newline at end of file diff --git a/src/files-ui/core/upload/utils.upload.ts b/src/files-ui/core/upload/utils.upload.ts new file mode 100644 index 0000000..b117684 --- /dev/null +++ b/src/files-ui/core/upload/utils.upload.ts @@ -0,0 +1,117 @@ +import { ExtFile, ExtFileInstance, Method, ServerResponse, UploadResponse, UPLOADSTATUS } from "../types" + +export const unexpectedErrorUploadResult = (extFile: ExtFile): UploadResponse => { + return { + id: extFile.id, + uploadedFile: + { + ...extFile, + uploadMessage: "Unable to upload. xhr object was not provided", + uploadStatus: "error" + }, + serverResponse: { + } + } +} +export const unableToUploadResult = ( + extFile: ExtFile +): UploadResponse => { + return { + id: extFile.id, + uploadedFile: { + ...extFile, + uploadMessage: "Unable to upload. XHR was not provided", + uploadStatus: "error" + }, + serverResponse: { + } + } +} +export const completeUploadResult = ( + extFile: ExtFile, + serverResponse: ServerResponse, + result: UPLOADSTATUS +): UploadResponse => { + return { + id: extFile.id, + uploadedFile: { + ...extFile, + uploadMessage: serverResponse.message, + uploadStatus: result + }, + serverResponse: serverResponse + } +} + +/** + * Initializes the xhr attribute for performing uploads + * @param extFileList the list of extended files + * @returns the array of extFiles with the xhr attribute initialized + */ +export const toUploadableExtFileList = ( + extFileList: ExtFile[] | ExtFileInstance[] + ): ExtFile[] => { + if (!extFileList) return []; + return extFileList.map(extFile => { + return { ...extFile, xhr: new XMLHttpRequest() } + }); +} + +/** + * Updates the uploadStatus of the given extFile + * from "preparing" to "uploading" + * @param extFile the extended file + * @returns the extended file with uploadStatus updated to "uploading" + */ +export const instantPreparingToUploadOne = ( + extFile: ExtFileInstance | ExtFile +): ExtFileInstance | ExtFile => { + if (extFile.uploadStatus === "preparing") { + //for ExtFile instance + extFile.uploadStatus = "uploading"; + //for ExtFile type + return { + ...extFile, + uploadStatus: "uploading", + }; + } + return extFile; +}; + +/** + * + * @param extFile the extended file + * @returns + */ +export const preparingToUploadOne = ( + extFile: ExtFileInstance | ExtFile +): Promise<ExtFileInstance | ExtFile> => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (extFile.uploadStatus === "preparing") { + //for ExtFile instance + extFile.uploadStatus = "uploading"; + //for ExtFile type + resolve({ + ...extFile, + uploadStatus: "uploading", + }); + } else + resolve(extFile); + }, 1500); + }); +}; +/** + * Sleeps for 1200 miliseconds for showing a better transition + * on uploading + * @returns true is everything is ok + */ +export const sleepTransition = ( +): Promise<boolean> => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(true); + }, 1200); + }); +} + diff --git a/src/pages/api/AvatarApi.jsx b/src/pages/api/AvatarApi.jsx new file mode 100644 index 0000000..ab2e367 --- /dev/null +++ b/src/pages/api/AvatarApi.jsx @@ -0,0 +1,10 @@ +import * as React from "react"; + +const AvatarApi = props =>{ + return( + <div> + AvatarApi + </div> + ) +} +export default AvatarApi; \ No newline at end of file diff --git a/src/pages/api/FileMosaicApi.jsx b/src/pages/api/FileMosaicApi.jsx index f30a2d0..e625b43 100644 --- a/src/pages/api/FileMosaicApi.jsx +++ b/src/pages/api/FileMosaicApi.jsx @@ -15,12 +15,12 @@ const rightMenuItems = [ { id: 0, label: "Demos", - referTo: "/api/file-mosaic/#filemosaic-demo", + referTo: "/api/file-mosaic#filemosaic-demo", }, { id: 1, label: "Props", - referTo: "/api/file-mosaic/#filemosaic-props", + referTo: "/api/file-mosaic#filemosaic-props", }, ]; const FileMosaicApi = (props) => { diff --git a/src/pages/api/InputButtonApi.jsx b/src/pages/api/InputButtonApi.jsx new file mode 100644 index 0000000..2009b71 --- /dev/null +++ b/src/pages/api/InputButtonApi.jsx @@ -0,0 +1,10 @@ +import * as React from "react"; + +const InputButtonApi = props =>{ + return( + <div> + InputButtonApi + </div> + ) +} +export default InputButtonApi; \ No newline at end of file diff --git a/src/pages/demo/AvatarDemoPage.tsx b/src/pages/demo/AvatarDemoPage.tsx new file mode 100644 index 0000000..10e2159 --- /dev/null +++ b/src/pages/demo/AvatarDemoPage.tsx @@ -0,0 +1,146 @@ +import { Alert, AlertTitle, Paper } from "@mui/material"; +import * as React from "react"; +import CodeHighlight from "../../components/codeHighlight/CodeHighlight"; +import BasicDemoAvatar from "../../components/demo-components/avatar-demo/BasicDemoAvatar"; +import DescParagraph from "../../components/demo-components/desc-paragraph/DescParagraph"; +import SubTitle from "../../components/demo-components/sub-title/SubTitle"; +import MainContentContainer from "../../components/layout-pages/MainContentContainer"; +import RightMenuContainer from "../../components/layout-pages/RightMenuContainer"; +import MainTitle from "../../components/main-title/MainTitle"; +import MainParagraph from "../../components/paragraph-main/MainParagraph"; +import RightMenu from "../../components/RightMenu/RightMenu"; +import TypeHighlight from "../../components/typeHighlight/TypeHighlight"; + +const rightMenuItems = [ + { + id: 0, + label: "Basic dropzone", + referTo: "/components/dropzone#basic-dropzone", + }, + { + id: 1, + label: "Validation", + referTo: "/components/dropzone#validation", + }, + { + id: 1, + label: "Custom validation", + referTo: "/components/dropzone#custom-validation", + }, + { + id: 2, + label: "Dropzone events", + referTo: "/components/dropzone#dropzone-events", + }, + { + id: 3, + label: "Uploading", + referTo: "/components/dropzone#uploading", + }, + { + id: 4, + label: "Styling", + referTo: "/components/dropzone#styling", + }, + { + id: 5, + label: "Localization", + referTo: "/components/dropzone#localization", + }, + { + id: 6, + label: "Dark mode", + referTo: "/components/dropzone#dark-mode", + }, +]; + +interface AvatarDemoPageProps {} +const AvatarDemoPage: React.FC<AvatarDemoPageProps> = ( + props: AvatarDemoPageProps +) => { + return ( + <React.Fragment> + <MainContentContainer> + <MainTitle>Avatar</MainTitle> + + <MainParagraph> + The "avatar" component is a special file{" "} + <CodeHighlight>input</CodeHighlight> designed for setting an image by + either dragging and dropping files there or by picking files from a + file dialog. + </MainParagraph> + + <DescParagraph> + You can consider that this widget is a kind of combination between + dropzone and file mosaic components. + <ol> + <li>The image</li> + <li> + The file(s) must be validated or not. If validation is required, + it can be done by taking into account a predefined set of allowed{" "} + <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept"> + Common MIME Types + </a> + ; specifiying the max file size (in bytes) and/or the max amount + of files. + </li> + <li>The file(s) must be uploaded somewhere in the internet.</li> + </ol> + </DescParagraph> + <DescParagraph> + It's meant to be an improved version of the "react-dropzone" and + "dropzone" packages. + </DescParagraph> + + <section id="basic-avatar"> + <SubTitle content="Basic Avatar" /> + <DescParagraph> + In this demo we set avatar with the minimun props that allows you to + get done fast. These props are{" "} + <CodeHighlight>onChange</CodeHighlight> and{" "} + <CodeHighlight>value</CodeHighlight>. + </DescParagraph> + <Paper + variant="outlined" + style={{ + padding: "25px", + display: "flex", + width: "100%", + alignItems: "center", + }} + > + <BasicDemoAvatar /> + </Paper> + {"<BasicDemoAvatarCode/>>"} + {/* <BasicDropzoneCodeJS /> */} + <Alert severity="info"> + <AlertTitle> FileMosaic </AlertTitle> + For completeness, these examples include{" "} + <CodeHighlight>{`<FileMosaic/>`} </CodeHighlight> + component for showing the files selected by the user with minimun + props too. For further information of this component check out the{" "} + <a href="/components/filemosaic">FileMosaic</a> page. + </Alert> + <br /> + <Alert severity="info"> + <AlertTitle> ExtFile </AlertTitle> + {/* This is an info alert — <strong>check it out!</strong> + */} + <strong>ExtFile type </strong> + is explicity used in the + <strong> Typescript</strong> example and is implicity used in the{" "} + <strong>Javascript</strong> example for handling the metadata that + makes possible the information exchange between components. For + further information about this data type check out the{" "} + <a href="/types#ext-file">ExtFile-API. </a> + </Alert> + </section> + </MainContentContainer> + + <RightMenuContainer> + <RightMenu width="240px" items={rightMenuItems} /> + </RightMenuContainer> + </React.Fragment> + ); +}; +export default AvatarDemoPage; diff --git a/src/pages/demo/DropzoneDemoPage.jsx b/src/pages/demo/DropzoneDemoPage.jsx index 8b41861..5fb0922 100644 --- a/src/pages/demo/DropzoneDemoPage.jsx +++ b/src/pages/demo/DropzoneDemoPage.jsx @@ -17,44 +17,43 @@ const rightMenuItems = [ { id: 0, label: "Basic dropzone", - referTo: "/components/dropzone/#basic-dropzone", + referTo: "/components/dropzone#basic-dropzone", }, { id: 1, label: "Validation", - referTo: "/components/dropzone/#validation", + referTo: "/components/dropzone#validation", }, { id: 1, label: "Custom validation", - referTo: "/components/dropzone/#custom-validation", + referTo: "/components/dropzone#custom-validation", }, { id: 2, label: "Dropzone events", - referTo: "/components/dropzone/#dropzone-events", + referTo: "/components/dropzone#dropzone-events", }, { id: 3, label: "Uploading", - referTo: "/components/dropzone/#uploading", + referTo: "/components/dropzone#uploading", }, { id: 4, label: "Styling", - referTo: "/components/dropzone/#styling", + referTo: "/components/dropzone#styling", }, { id: 5, label: "Localization", - referTo: "/components/dropzone/#localization", + referTo: "/components/dropzone#localization", }, { id: 6, label: "Dark mode", - referTo: "/components/dropzone/#dark-mode", + referTo: "/components/dropzone#dark-mode", }, - ]; const DropzoneDemoPage = (props) => { return ( @@ -80,18 +79,15 @@ const DropzoneDemoPage = (props) => { and dropped into the widget </li> <li> - The file(s) must be validated or not taking into account a - predefined set of allowed{" "} + The file(s) must be validated or not. If validation is required, + it can be done by taking into account a predefined set of allowed{" "} <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept"> Common MIME Types </a> - ; specifiying the max file size (in bytes) or max amout of files. + ; specifiying the max file size (in bytes) and/or the max amount + of files. </li> <li>The file(s) must be uploaded somewhere in the internet.</li> - <li> - The file(s) must be shown in the screen with a preview according - to the file type. - </li> </ol> </DescParagraph> <DescParagraph> @@ -104,8 +100,8 @@ const DropzoneDemoPage = (props) => { <DescParagraph> In this demo we set dropzone with the minimun props that allows you to get done fast. These props are{" "} - <code className="code">onChange</code> and{" "} - <code className="code">value</code> props. + <CodeHighlight>onChange</CodeHighlight> and{" "} + <CodeHighlight>value</CodeHighlight>. </DescParagraph> <Paper variant="outlined" style={{ padding: "25px" }}> <BasicDemoDropzone /> @@ -114,7 +110,7 @@ const DropzoneDemoPage = (props) => { <Alert severity="info"> <AlertTitle> FileMosaic </AlertTitle> For completeness, these examples include{" "} - <strong>{`<FileMosaic/>`} </strong> + <CodeHighlight>{`<FileMosaic/>`} </CodeHighlight> component for showing the files selected by the user with minimun props too. For further information of this component check out the{" "} <a href="/components/filemosaic">FileMosaic</a> page. @@ -134,43 +130,52 @@ const DropzoneDemoPage = (props) => { </Alert> </section> - {/* <section id="validation"> - <SubTitle content="Validation" /> - <DescParagraph> - You can either "tell" Dropzone component to validate user files by - providing one or more of these criteria: - <ol> - <li>Accept specific file types.</li> - <li>Accept an specific number of files.</li> - <li>Accept an specific size (in bytes) of files.</li> - </ol> - </DescParagraph> - - <Paper variant="outlined" style={{ padding: "25px" }}> - <BasicDemoDropzone /> - </Paper> + <section id="validation"> + <SubTitle content="Validation" /> + <DescParagraph> + You can either "tell" Dropzone component to validate user files by + providing one or more of these criteria: + <ol> + <li>Accept specific file types.</li> + <li>Accept an specific number of files.</li> + <li>Accept an specific size (in bytes) of files.</li> + </ol> + </DescParagraph> - <p></p> - <BasicDropzoneCodeJS /> - </section> + <Paper variant="outlined" style={{ padding: "25px" }}> + <BasicDemoDropzone /> + </Paper> - <section id="custom-validation"> - <SubTitle content="Custom validation" /> - <DescParagraph> - You can also "override the Dropzone validation by performimg a custom - validation using a custom function that must fit the following - signature: - <div>... type</div> - </DescParagraph> + <p></p> + <BasicDropzoneCodeJS /> + </section> - <Paper variant="outlined" style={{ padding: "25px" }}> - <BasicDemoDropzone /> - </Paper> + <section id="custom-validation"> + <SubTitle content="Custom validation" /> + <DescParagraph> + You can also "override the Dropzone validation by giving a custom + validation function that must fit the following signature:{" "} + <CodeHighlight> + {"validator?: (f: "} + <a href="https://developer.mozilla.org/en-US/docs/Web/API/File"> + File + </a> + {") => "} + <a href="/types#custom-validate-file-response"> + CustomValidateFileResponse + </a> + </CodeHighlight> + . + </DescParagraph> - <p></p> - <BasicDropzoneCodeJS /> - </section> + <Paper variant="outlined" style={{ padding: "25px" }}> + <BasicDemoDropzone /> + </Paper> + <p></p> + <BasicDropzoneCodeJS /> + </section> + {/* <section id="dropzone-events"> <SubTitle content="Dropzone events" /> <DescParagraph> diff --git a/src/pages/demo/FileCardDemoPage.jsx b/src/pages/demo/FileCardDemoPage.jsx index e8baae8..ffa5c15 100644 --- a/src/pages/demo/FileCardDemoPage.jsx +++ b/src/pages/demo/FileCardDemoPage.jsx @@ -101,46 +101,46 @@ const rightMenuItems = [ { id: 0, label: "Basic file mosaic", - referTo: "/components/file-mosaic/#basic-filemosaic", + referTo: "/components/file-mosaic#basic-filemosaic", }, { id: 1, label: "Image Preview", - referTo: "/components/file-mosaic/#file-mosaic-image-preview", + referTo: "/components/file-mosaic#file-mosaic-image-preview", }, { id: 2, label: "Validation", - referTo: "/components/file-mosaic/#file-mosaic-validation", + referTo: "/components/file-mosaic#file-mosaic-validation", }, { id: 3, label: "Uploading", - referTo: "/components/file-mosaic/#file-mosaic-uploading", + referTo: "/components/file-mosaic#file-mosaic-uploading", }, { id: 4, label: "Localization", - referTo: "/components/file-mosaic/#file-mosaic-localization", + referTo: "/components/file-mosaic#file-mosaic-localization", }, { id: 5, label: "Previews", - referTo: "/components/file-mosaic/#file-mosaic-previews", + referTo: "/components/file-mosaic#file-mosaic-previews", }, { id: 6, label: "Actions", - referTo: "/components/file-mosaic/#actions", + referTo: "/components/file-mosaic#actions", }, { id: 7, label: "Default previews", - referTo: "/components/file-mosaic/#default-previews", + referTo: "/components/file-mosaic#default-previews", }, { id: 8, label: "Dark mode", - referTo: "/components/file-mosaic/#dark-mode", + referTo: "/components/file-mosaic#dark-mode", }, ]; diff --git a/src/pages/demo/FileMosaicDemoPage.jsx b/src/pages/demo/FileMosaicDemoPage.jsx index 1756288..cff97b5 100644 --- a/src/pages/demo/FileMosaicDemoPage.jsx +++ b/src/pages/demo/FileMosaicDemoPage.jsx @@ -24,8 +24,8 @@ const FileMosaicDemoPage = (props) => { allow the user to interact with them. </MainParagraph> <DescParagraph> - This widget allow users to see information of Files and / or trigger - actions. + This widget allow users to see information of{" "} + <TypeHighlight> Files</TypeHighlight> and / or trigger actions. </DescParagraph> <Alert severity="info"> While included here as a standalone component, the most common use @@ -100,46 +100,46 @@ const rightMenuItems = [ { id: 0, label: "Basic file mosaic", - referTo: "/components/file-mosaic/#basic-filemosaic", + referTo: "/components/file-mosaic#basic-filemosaic", }, { id: 1, label: "Image Preview", - referTo: "/components/file-mosaic/#file-mosaic-image-preview", + referTo: "/components/file-mosaic#file-mosaic-image-preview", }, { id: 2, label: "Validation", - referTo: "/components/file-mosaic/#file-mosaic-validation", + referTo: "/components/file-mosaic#file-mosaic-validation", }, { id: 3, label: "Uploading", - referTo: "/components/file-mosaic/#file-mosaic-uploading", + referTo: "/components/file-mosaic#file-mosaic-uploading", }, { id: 4, label: "Localization", - referTo: "/components/file-mosaic/#file-mosaic-localization", + referTo: "/components/file-mosaic#file-mosaic-localization", }, { id: 5, label: "Previews", - referTo: "/components/file-mosaic/#file-mosaic-previews", + referTo: "/components/file-mosaic#file-mosaic-previews", }, { id: 6, label: "Actions", - referTo: "/components/file-mosaic/#actions", + referTo: "/components/file-mosaic#actions", }, { id: 7, label: "Default previews", - referTo: "/components/file-mosaic/#default-previews", + referTo: "/components/file-mosaic#default-previews", }, { id: 8, label: "Dark mode", - referTo: "/components/file-mosaic/#dark-mode", + referTo: "/components/file-mosaic#dark-mode", }, ]; diff --git a/src/pages/demo/InputButtonDemoPage.tsx b/src/pages/demo/InputButtonDemoPage.tsx new file mode 100644 index 0000000..bf808d2 --- /dev/null +++ b/src/pages/demo/InputButtonDemoPage.tsx @@ -0,0 +1,10 @@ +import * as React from "react"; +interface InputButtonDemoPageProps{} +const InputButtonDemoPage:React.FC<InputButtonDemoPageProps> = (props:InputButtonDemoPageProps) =>{ + return( + <div> + InputButtonDemoPage + </div> + ) +} +export default InputButtonDemoPage; \ No newline at end of file diff --git a/src/pages/getting-started/GettingStartedPage.jsx b/src/pages/getting-started/GettingStartedPage.jsx index 19fa607..6516893 100644 --- a/src/pages/getting-started/GettingStartedPage.jsx +++ b/src/pages/getting-started/GettingStartedPage.jsx @@ -21,7 +21,7 @@ import RightMenuContainer from "../../components/layout-pages/RightMenuContainer const GettingStartedPage = ({ darkModeOn }) => { return ( <MainLayoutPage selectedIndex={0}> - <MainContentContainer > + <MainContentContainer> <Stack sx={{ width: "100%", alignItems: "center" }}> <img className="fui-logo-img-getting-started" @@ -75,18 +75,17 @@ const rightMenuItems = [ { id: 0, label: "Overview", - referTo: "/getting-started/#overview", + referTo: "/getting-started#overview", }, { id: 1, label: "Installation", - referTo: "/getting-started/#installation", + referTo: "/getting-started#installation", }, { id: 2, label: "Peer dependency", - referTo: "/getting-started/#peer-dependency", + referTo: "/getting-started#peer-dependency", }, - { id: 3, label: "Default font", referTo: "/getting-started/#default-font" }, - + { id: 3, label: "Default font", referTo: "/getting-started#default-font" }, ]; diff --git a/src/pages/usage/UsagePage.jsx b/src/pages/usage/UsagePage.jsx index 611c2a4..ba05fd9 100644 --- a/src/pages/usage/UsagePage.jsx +++ b/src/pages/usage/UsagePage.jsx @@ -19,11 +19,11 @@ import RightMenuContainer from "../../components/layout-pages/RightMenuContainer import MainContentContainer from "../../components/layout-pages/MainContentContainer"; import MainTitle from "../../components/main-title/MainTitle"; const rightMenuItems = [ - { id: 0, label: "Quick start", referTo: "/usage/#quick-start" }, + { id: 0, label: "Quick start", referTo: "/usage#quick-start" }, { id: 1, label: "Advanced examples", - referTo: "/usage/#advanced-example", + referTo: "/usage#advanced-example", }, ]; const UsagePage = (props) => { @@ -48,7 +48,7 @@ const UsagePage = (props) => { <CodeHighlight>onChange</CodeHighlight> and{" "} <CodeHighlight>value</CodeHighlight> props. This example is the same as the one you will find in the{" "} - <a href="/components/dropzone/#basic-dropzone">Basic dropzone</a>{" "} + <a href="/components/dropzone#basic-dropzone">Basic dropzone</a>{" "} section. </DescParagraph>{" "} <BasicDropzoneCodeJS splittedOnly /> @@ -57,7 +57,7 @@ const UsagePage = (props) => { </Paper>{" "} */} <DescParagraph> You can play around with this code in the interactive Code Sandbox - demo below. Try changing the <CodeHighlight>label</CodeHighlight> on + demo below. Try adding the <CodeHighlight>accept</CodeHighlight> prop to the Dropzone to see the changes: </DescParagraph> {/* <iframe diff --git a/src/routes/MainRouter.jsx b/src/routes/MainRouter.jsx index bc65ae2..eaf9743 100644 --- a/src/routes/MainRouter.jsx +++ b/src/routes/MainRouter.jsx @@ -16,6 +16,10 @@ import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom"; import MainLayoutPage from "../components/layout-pages/MainLayoutPage"; import FileReaderPage from "../pages/utilities/FileReaderPage"; import FileUploaderPage from "../pages/utilities/FileUploaderPage"; +import InputButtonDemoPage from "../pages/demo/InputButtonDemoPage"; +import AvatarDemoPage from "../pages/demo/AvatarDemoPage"; +import InputButtonApi from "../pages/api/InputButtonApi"; +import AvatarApi from "../pages/api/AvatarApi"; const router = createBrowserRouter([ { @@ -43,10 +47,18 @@ const router = createBrowserRouter([ </MainLayoutPage> ), children: [ + { + path: "/components/avatar", + element: <AvatarDemoPage />, + }, { path: "/components/dropzone", element: <DropzoneDemoPage />, }, + { + path: "/components/inputbutton", + element: <InputButtonDemoPage />, + }, { path: "/components/filemosaic", element: <FileMosaicDemoPage />, @@ -65,6 +77,14 @@ const router = createBrowserRouter([ </MainLayoutPage> ), children: [ + { + path: "/api/avatar", + element: <AvatarApi />, + }, + { + path: "/api/inputbutton", + element: <InputButtonApi />, + }, { path: "/api/dropzone", element: <DropzoneApi />, diff --git a/src/static/new-icons/Microsoft.VisualStudio.Services.Icons.Default.png b/src/static/new-icons/Microsoft.VisualStudio.Services.Icons.Default.png new file mode 100644 index 0000000000000000000000000000000000000000..386be5b3030db509f506f11abf97af66ab29c4ec GIT binary patch literal 3546 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 Utm!+Q_y7O^07*qoM6N<$f}km{4gdfE literal 0 HcmV?d00001 diff --git a/src/static/new-icons/gradle.jpg b/src/static/new-icons/gradle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a87d77a1058c57306cd434599ae6bd487489af7 GIT binary patch literal 4879 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 TZE`J^PXV;Sxql^PJD~p;+<^y& literal 0 HcmV?d00001 diff --git a/src/static/new-icons/images.png b/src/static/new-icons/images.png new file mode 100644 index 0000000000000000000000000000000000000000..ca61510a9d20959d3740958f27e0702b91fe1986 GIT binary patch literal 892 zcmV-?1B3jDP)<h;3K|Lk000e1NJLTq004pj004pr0{{R3z1`r?0001NP)t-s?A1R2 z008vdLHOW8>eD~(*g)OKI~W!Q6%z$5DGXy;8ay@-iF_s0zd6IOGz|>|Kspg4APKRc zFLrAmO+^&Swl=z`GULrXH!=@tUmTf=DJLTeR!$dQRT++fCLbIKeQ_bEmn}y?6L4i7 zF7lhm0008ZNkl<Zc%1E**?Oul6h<=;5GrDw0JYZY`@iFn43G`9U!q=|i+^3=PDmEn znPdzA0000000000000000000000000000000Pt4CEXpfWliI^;b8RUWhvbWF!lK%D z?W9dt#?ztA<W^-_)k?<BkB6)(h=XXmAiqaCNIr{E$n~?=Wb)Ak3dzJ#7oAeSt=iN- zykCJI`fu$ORBehjUU%wXVq&J7R~7PPqQK-%7wx3i)RzujpH!$QRDmyDM}vaen4;Rt zQl%rS`03d3y(%d+^A&ih#tJO`)FnTChgK?&HLwbwsiH8da268<JM2Z@qu81ei_L>m zDCgV<1^y$(3U2cCnNW1V-mZ|Fo;(FsH@=)x+QkY4EAXovE7*bmvTxd-$LKutb3sg} zf>k&f2fapo1+^z5Uz>VqbROJ_*sKVNr*J%*RLGG6Q@QuuHV;L-!7p2r`7)z$Db0HC zmLE|N7ad<~1=;qM-lFjBD=g(w2dl@7q@!RmQR&L|d`SN4>v;u{nC4qS6_{-LOt?JA zzxs+%$f$x?neahS1$SJ^J`b^nq_6O?_PJ37CJJ4tQH&_4GSl(LDiHc1e<lxB;Z#e4 zzd=6~RBA+ziYPE~wwbVb*glhofUT+9^zD%I^hOoT$+Tw5>`VKgJkZNwr~*sO_28?a zldd+ID3xBfx1c<z!^V<EDTuu{TL%So*MW;E4Lc;&+O=bE7J&*(TxS)`95}abT81d> zo>Z`Nex%?>`7UflzM!y~RqzJq_@ct7UNTW|E08Lv)u0e~NdBTQ;U*>us+>_^;?(CM za8n3XI1e}IexAF{fht_wE1SU0`4t7mnyG@jxbQq>VW`^+3NG>VJhUHGgx=WHWos#& z!eWNXC^Xe#;g5p6ceQ!zD?HNEy%F&bW_&+n$vi_Dc{`beTnnKB0002M|H3~lQ6_zr S(|HsC0000<MNUMnLSTZPR*=X5 literal 0 HcmV?d00001 diff --git a/src/static/new-icons/markdown-fill-1.png b/src/static/new-icons/markdown-fill-1.png new file mode 100644 index 0000000000000000000000000000000000000000..37a31624e2eba4cfda902bea852d896680c881c4 GIT binary patch literal 5937 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 literal 0 HcmV?d00001 diff --git a/src/static/new-icons/maven_logo.png b/src/static/new-icons/maven_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..990203c90a2fcdcffbdfc8b4de975ef237e0107c GIT binary patch literal 20606 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 D50|Qk literal 0 HcmV?d00001 diff --git a/Captura de pantalla (3946).png b/src/static/other/Captura de pantalla (3946).png similarity index 100% rename from Captura de pantalla (3946).png rename to src/static/other/Captura de pantalla (3946).png diff --git a/ref mt.png b/src/static/other/ref mt.png similarity index 100% rename from ref mt.png rename to src/static/other/ref mt.png diff --git a/src/templates/NavBarTemplate.jsx b/src/templates/NavBarTemplate.jsx index 273e8bd..6886ff9 100644 --- a/src/templates/NavBarTemplate.jsx +++ b/src/templates/NavBarTemplate.jsx @@ -75,9 +75,9 @@ function NavBarTemplate(props) { <img src={darkModeOn ? logo_text_blue_dark : logo_text_blue} alt="files-ui-main-logo-text" - height={20} + height={16} /> - {/* <Typography variant="h6" noWrap component="div" color="primary"> + {/* <Typography variant="h6" noWrap component="div" color="primary"> Files ui </Typography> */} </Stack> diff --git a/src/tester/AvatarTester.tsx b/src/tester/AvatarTester.tsx new file mode 100644 index 0000000..9e691c9 --- /dev/null +++ b/src/tester/AvatarTester.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; +import { Avatar } from "../files-ui"; +import { + ServerResponse, + //UploadPromiseResponse, + //uploadPromiseXHR, +} from "../files-ui/core"; + +import { uploadFile } from "../files-ui/core"; + +export const resultURL: string = + "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ1YdE-2sQT4na6RwujeATyMBcCXqg0Gf76TYTplRkMkq0wIi_SewCDq6VeUGxPwpK_Qd8&usqp=CAU"; +export const initURL: string = + "https://sm.ign.com/ign_latam/screenshot/default/goku-ssj-blue-3_6qjv.jpg"; + +const rowStyle: React.CSSProperties = { + display: "flex", + flexDirection: "row", + padding: "20px", + border: "1px dotted orange", + borderRadius: "8px", + boxSizing: "border-box", + margin: "25px 0", + backgroundColor: "white", + gap: "15px", +}; + + +const REMOTE = "https://files-ui-express-static-file-storage.vercel.app/39d33dff2d41b522c1ea276c4b82507f96b9699493d2e7b3f5c864ba743d9503"; +const LOCAL = "http://localhost/39d33dff2d41b522c1ea276c4b82507f96b9699493d2e7b3f5c864ba743d9503"; + + +interface AvatarTesterProps {} +const AvatarTester: React.FC<AvatarTesterProps> = ( + props: AvatarTesterProps +) => { + const [avatarSrc, setAvatarSrc] = React.useState<string | undefined>( + undefined + ); + + /* const handleChange = async (file: File) => { + const endpoint: string = "http://localhost:2800/upload-avatar"; + const response: UploadPromiseResponse = await uploadPromiseXHR( + { id: 0, file: file, xhr: new XMLHttpRequest() }, + endpoint, + "POST", + { + Authorization: "bearer bJUKYIBVUKIBHUKYIBHVKULIUBHKLÑBJ", + "z-header": "HAAAAAAA", + } + ); + // const res: boolean = uploadFile(file, endpoint); + const serverResponse: ServerResponse = response.uploadResponse + .serverResponse as ServerResponse; + const { url } = serverResponse.payload; + setAvatarSrc(url); + console.log(serverResponse.payload); + }; */ + + const handleChange2 = async (file: File) => { + const endpoint: string = REMOTE + "/file/28048465460"; + const res: ServerResponse = await uploadFile(file, endpoint); + const { url } = res.payload; + setAvatarSrc(url); + }; + + return ( + <React.Fragment> + <div style={rowStyle}> + <Avatar + src={avatarSrc} + variant="circle" + style={{ width: "300px", height: "300px" }} + onChange={handleChange2} + /> + </div> + </React.Fragment> + ); +}; +export default AvatarTester; diff --git a/use-cases.md b/use-cases.md new file mode 100644 index 0000000..8cc66f9 --- /dev/null +++ b/use-cases.md @@ -0,0 +1,2 @@ +# Files -ui Use Cases +- set the thumbnail, after loads the hd image show loader \ No newline at end of file -- GitLab