* refactor admin settings feature * separated save-restart bar to separate component * created new CE dir to facilitate code split * created separate ee dir and exporting everything we have in ce file. * little mod * minor fix * splitting settings types config * using object literals for category types instead of enums * CE: support use of component for each category * minor style fix * authentication page UI changes implemented * github signup doc url added back * removed comments * routing updates * made subcategories listing in left pane optional * added muted saml to auth listing * added breadcrumbs and enabled button * created separate component for auth page and auth config * added callout and disconnect components * updated breadcrumbs component * minor updates to common components * updated warning callout and added icon * ce: test cases fixed * updated test file name * warning banner callout added on auth page * updated callout banner for form login * CE: Split config files * CE: moved the window declaration in EE file as its dependency will be updated in EE * CE: Splitting ApiConstants and SocialLogin constants * CE: split login page * CE: moved getSocialLoginButtonProps func to EE file as it's dependencies will be updated in EE * added key icon * CE: created a factory class to share social auths list * Minor style fix for social btns * Updated the third party auth styles * Small fixes to styling * ce: splitting forms constants * breadcrumbs implemented for all pages in admin settings * Settings breadcrumbs separated * splitted settings breadcrumbs between ce and ee * renamed default import * minor style fix * added login form config. * updated login/signup pages to use form login disabled config * removed common functionality outside * implemented breadcrumb component from scratch without using blueprint * removed unwanted code * Small style update * updated breadcrumb categories file name and breadcrumb icon * added cypress tests for admin settings auth page * added comments * update locator for upgrade button * added link for intercom on upgrade button * removed unnecessary file * minor style fix * style fix for auth option cards * split messages constant * fixed imports for message constants splitting. * added message constants * updated unit test cases * fixed messages import in cypress index * fixed messages import again, cypress fails to read re-exported objs. * added OIDC auth method on authentication page * updated import statements from ee to @appsmith * removed dead code * updated read more link UI * PR comments fixes * some UI fixes * used color and fonts from theme * fixed some imports * fixed some imports * removed warning imports * updated OIDC logo and auth method desc copies * css changes * css changes * css changes * updated cypress test for breadcrumb * moved callout component to ads as calloutv2 * UI changes for form fields * updated css for spacing between form fields * added sub-text on auth pages * added active class for breadcrumb item * added config for disable signup toggle and fixed UI issues of restart banner * fixed admin settings page bugs * assigned true as default state for signup * fixed messages import statements * updated code for PR comments related suggestions * reverted file path change in cypress support * updated cypress test * updated cypress test Co-authored-by: Ankita Kinger <ankita@appsmith.com>
318 lines
8.9 KiB
TypeScript
318 lines
8.9 KiB
TypeScript
import React, { useEffect, useRef, useState } from "react";
|
|
import styled from "styled-components";
|
|
import Button, { Category, Size } from "./Button";
|
|
import { ReactComponent as UploadIcon } from "../../assets/icons/ads/upload-v2.svg";
|
|
import { ReactComponent as UploadSuccessIcon } from "../../assets/icons/ads/upload_success.svg";
|
|
import { DndProvider, useDrop, DropTargetMonitor } from "react-dnd";
|
|
import HTML5Backend, { NativeTypes } from "react-dnd-html5-backend";
|
|
import Text, { TextType } from "./Text";
|
|
import { Variant } from "./common";
|
|
import { Toaster } from "./Toast";
|
|
import {
|
|
createMessage,
|
|
ERROR_FILE_TOO_LARGE,
|
|
REMOVE_FILE_TOOL_TIP,
|
|
} from "@appsmith/constants/messages";
|
|
import TooltipComponent from "components/ads/Tooltip";
|
|
import { Position } from "@blueprintjs/core/lib/esm/common/position";
|
|
import Icon, { IconSize } from "./Icon";
|
|
import {
|
|
ContainerDiv,
|
|
FileEndings,
|
|
FileType,
|
|
FilePickerProps,
|
|
} from "./FilePicker";
|
|
|
|
const ContainerDivWithBorder = styled(ContainerDiv)<{
|
|
isUploaded: boolean;
|
|
isActive: boolean;
|
|
canDrop: boolean;
|
|
fileType: FileType;
|
|
}>`
|
|
width: 100%;
|
|
height: 188px;
|
|
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23F86A2B' stroke-width='1.2' stroke-dasharray='6.4%2c 6.4' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
|
|
background-color: ${(props) => props.theme.colors.homepageBackground};
|
|
`;
|
|
|
|
const IconWrapper = styled.div`
|
|
width: ${(props) => props.theme.spaces[9]}px;
|
|
padding-left: ${(props) => props.theme.spaces[2]}px;
|
|
`;
|
|
|
|
function FilePickerComponent(props: FilePickerProps) {
|
|
const { fileType, logoUploadError } = props;
|
|
const [fileInfo, setFileInfo] = useState<{ name: string; size: number }>({
|
|
name: "",
|
|
size: 0,
|
|
});
|
|
const [isUploaded, setIsUploaded] = useState<boolean>(false);
|
|
const [fileUrl, setFileUrl] = useState("");
|
|
|
|
const [{ canDrop, isOver }, drop] = useDrop({
|
|
accept: [NativeTypes.FILE],
|
|
drop(item, monitor) {
|
|
onDrop(monitor);
|
|
},
|
|
collect: (monitor) => ({
|
|
isOver: monitor.isOver(),
|
|
canDrop: monitor.canDrop(),
|
|
}),
|
|
});
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const bgRef = useRef<HTMLDivElement>(null);
|
|
const progressRef = useRef<HTMLDivElement>(null);
|
|
const fileDescRef = useRef<HTMLDivElement>(null);
|
|
const fileContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
function ButtonClick(event: React.MouseEvent<HTMLElement>) {
|
|
event.preventDefault();
|
|
if (inputRef.current) {
|
|
inputRef.current.click();
|
|
}
|
|
}
|
|
|
|
function onDrop(monitor: DropTargetMonitor) {
|
|
if (monitor) {
|
|
const files = monitor.getItem().files;
|
|
if (!files) {
|
|
return;
|
|
}
|
|
handleFileUpload(files);
|
|
}
|
|
}
|
|
|
|
function setProgress(uploadPercentage: number) {
|
|
if (progressRef.current) {
|
|
progressRef.current.style.width = `${uploadPercentage}%`;
|
|
}
|
|
if (uploadPercentage === 100) {
|
|
setIsUploaded(true);
|
|
if (fileDescRef.current && bgRef.current && fileType === FileType.IMAGE) {
|
|
fileDescRef.current.style.display = "none";
|
|
bgRef.current.style.opacity = "1";
|
|
}
|
|
}
|
|
}
|
|
|
|
function onUpload(url: string) {
|
|
props.onFileUploaded && props.onFileUploaded(url);
|
|
}
|
|
|
|
function handleFileUpload(files: FileList | null) {
|
|
if (fileType === FileType.IMAGE) {
|
|
handleImageFileUpload(files);
|
|
} else {
|
|
handleOtherFileUpload(files);
|
|
}
|
|
}
|
|
|
|
function handleOtherFileUpload(files: FileList | null) {
|
|
const file = files && files[0];
|
|
let fileSize = 0;
|
|
if (!file) {
|
|
return;
|
|
}
|
|
fileSize = Math.floor(file.size / 1024);
|
|
setFileInfo({ name: file.name, size: fileSize });
|
|
if (props.delayedUpload) {
|
|
setIsUploaded(true);
|
|
setProgress(100);
|
|
}
|
|
if (fileDescRef.current) {
|
|
fileDescRef.current.style.display = "flex";
|
|
}
|
|
if (fileContainerRef.current) {
|
|
fileContainerRef.current.style.display = "none";
|
|
}
|
|
props.fileUploader && props.fileUploader(file, setProgress, onUpload);
|
|
}
|
|
|
|
function handleImageFileUpload(files: FileList | null) {
|
|
const file = files && files[0];
|
|
let fileSize = 0;
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
fileSize = Math.floor(file.size / 1024);
|
|
setFileInfo({ name: file.name, size: fileSize });
|
|
|
|
if (fileSize < 250) {
|
|
if (bgRef.current) {
|
|
bgRef.current.style.backgroundImage = `url(${URL.createObjectURL(
|
|
file,
|
|
)})`;
|
|
bgRef.current.style.opacity = "0.5";
|
|
}
|
|
if (fileDescRef.current) {
|
|
fileDescRef.current.style.display = "block";
|
|
}
|
|
if (fileContainerRef.current) {
|
|
fileContainerRef.current.style.display = "none";
|
|
}
|
|
|
|
/* set form data and send api request */
|
|
props.fileUploader && props.fileUploader(file, setProgress, onUpload);
|
|
} else {
|
|
Toaster.show({
|
|
text: createMessage(ERROR_FILE_TOO_LARGE, "250 KB"),
|
|
variant: Variant.warning,
|
|
});
|
|
}
|
|
}
|
|
|
|
function removeFile() {
|
|
if (fileContainerRef.current) {
|
|
setFileUrl("");
|
|
if (fileDescRef.current) {
|
|
fileDescRef.current.style.display = "none";
|
|
}
|
|
fileContainerRef.current.style.display = "flex";
|
|
if (bgRef.current) {
|
|
bgRef.current.style.backgroundImage = "url('')";
|
|
}
|
|
setIsUploaded(false);
|
|
props.onFileRemoved && props.onFileRemoved();
|
|
}
|
|
}
|
|
|
|
const isActive = canDrop && isOver;
|
|
|
|
useEffect(() => {
|
|
if (props.url) {
|
|
const urlKeys = props.url.split("/");
|
|
if (urlKeys[urlKeys.length - 1] !== "null") {
|
|
setFileUrl(props.url);
|
|
} else {
|
|
setFileUrl("");
|
|
}
|
|
}
|
|
}, [props.url]);
|
|
|
|
// Following hook should be used only if file type is image.
|
|
useEffect(() => {
|
|
if (fileUrl && !isUploaded && fileType === FileType.IMAGE) {
|
|
setIsUploaded(true);
|
|
if (bgRef.current) {
|
|
bgRef.current.style.backgroundImage = `url(${fileUrl})`;
|
|
bgRef.current.style.opacity = "1";
|
|
}
|
|
if (fileDescRef.current) {
|
|
fileDescRef.current.style.display = "none";
|
|
}
|
|
if (fileContainerRef.current) {
|
|
fileContainerRef.current.style.display = "none";
|
|
}
|
|
}
|
|
}, [fileUrl, logoUploadError]);
|
|
|
|
// <UploadSuccessIcon />
|
|
|
|
const uploadFileForm = (
|
|
<div className="button-wrapper" ref={fileContainerRef}>
|
|
<UploadIcon />
|
|
<Text className="drag-drop-text" type={TextType.P2}>
|
|
Drag & Drop files to upload or
|
|
</Text>
|
|
<form>
|
|
<input
|
|
accept={FileEndings[fileType]}
|
|
id="fileInput"
|
|
multiple={false}
|
|
onChange={(el) => handleFileUpload(el.target.files)}
|
|
ref={inputRef}
|
|
type="file"
|
|
value={""}
|
|
/>
|
|
<Button
|
|
category={Category.tertiary}
|
|
onClick={(el) => ButtonClick(el)}
|
|
size={Size.medium}
|
|
text="Browse"
|
|
/>
|
|
</form>
|
|
</div>
|
|
);
|
|
|
|
const uploadStatus = (
|
|
<div className="file-spec">
|
|
<Text type={TextType.P1}>{fileInfo.name}</Text>
|
|
<Text type={TextType.P1}>{fileInfo.size}KB</Text>
|
|
</div>
|
|
);
|
|
|
|
const imageUploadComponent = (
|
|
<>
|
|
<div className="upload-form-container" ref={bgRef}>
|
|
{uploadFileForm}
|
|
<div className="file-description" id="fileDesc" ref={fileDescRef}>
|
|
{uploadStatus}
|
|
<div className="progress-container">
|
|
<div className="progress-inner" ref={progressRef} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="remove-button">
|
|
<Button
|
|
category={Category.tertiary}
|
|
icon="delete"
|
|
onClick={() => removeFile()}
|
|
size={Size.medium}
|
|
text="remove"
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const uploadComponent = (
|
|
<div className="upload-form-container">
|
|
{uploadFileForm}
|
|
<div
|
|
className="file-description centered"
|
|
id="fileDesc"
|
|
ref={fileDescRef}
|
|
>
|
|
{uploadStatus}
|
|
<div className="success-container">
|
|
<UploadSuccessIcon className="success-icon" />
|
|
<Text className="success-text" type={TextType.H4}>
|
|
Successfully Uploaded!
|
|
</Text>
|
|
<TooltipComponent
|
|
content={REMOVE_FILE_TOOL_TIP()}
|
|
position={Position.TOP}
|
|
>
|
|
<IconWrapper className="icon-wrapper" onClick={() => removeFile()}>
|
|
<Icon name="close" size={IconSize.XL} />
|
|
</IconWrapper>
|
|
</TooltipComponent>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<ContainerDivWithBorder
|
|
canDrop={canDrop}
|
|
fileType={fileType}
|
|
isActive={isActive}
|
|
isUploaded={isUploaded}
|
|
ref={drop}
|
|
>
|
|
{fileType === FileType.IMAGE ? imageUploadComponent : uploadComponent}
|
|
</ContainerDivWithBorder>
|
|
);
|
|
}
|
|
|
|
function FilePickerV2(props: FilePickerProps) {
|
|
return (
|
|
<DndProvider backend={HTML5Backend}>
|
|
<FilePickerComponent {...props} />
|
|
</DndProvider>
|
|
);
|
|
}
|
|
|
|
export default FilePickerV2;
|