Skip to content

Commit

Permalink
feat: Update UI for "download datasets" (#5796)
Browse files Browse the repository at this point in the history
Co-authored-by: Fran McDade <franmcdade@Frans-MacBook-Pro.local>
Co-authored-by: Fran McDade <frano-m@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 2, 2023
1 parent 522ae3c commit 7e3ccca
Show file tree
Hide file tree
Showing 26 changed files with 864 additions and 350 deletions.
1 change: 1 addition & 0 deletions frontend/src/common/featureFlags/features.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum FEATURES {
DOWNLOAD_UX = "dux",
GENE_SETS = "gs",
CURATOR = "curator",
FILTER = "filter",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Spinner } from "@blueprintjs/core";
import * as React from "react";
import { FC, useCallback } from "react";
import React, { ElementType, FC, useState } from "react";
import { useDatasetAssets } from "src/components/Collection/components/CollectionDatasetsGrid/components/Row/DownloadDataset/util";
import Content from "src/components/Collections/components/Dataset/components/DownloadDataset/components/Content";
import { StyledButton } from "src/components/Collections/components/Dataset/components/DownloadDataset/style";
import Modal from "src/components/common/Modal";
import { ModalContentWrapper } from "./style";
import {
Dialog as DownloadUXDialog,
DownloadDialog,
} from "src/components/Datasets/components/DownloadDataset/style";
import { useFeatureFlag } from "src/common/hooks/useFeatureFlag";
import { FEATURES } from "src/common/featureFlags/features";

interface Props {
Button?: React.ElementType;
Button: ElementType;
datasetId: string;
isDisabled?: boolean;
name: string;
Expand All @@ -21,18 +22,14 @@ interface Props {
* demand.
*/
const DownloadDataset: FC<Props> = ({
Button = StyledButton,
Button,
datasetId,
isDisabled = false,
name,
}) => {
// Open state of download modal.
const [isOpen, setIsOpen] = React.useState(false);

// Function toggling open state of modal.
const toggleOpen = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen]);
const isDownloadUX = useFeatureFlag(FEATURES.DOWNLOAD_UX);
const [isOpen, setIsOpen] = useState<boolean>(false);
const Dialog = isDownloadUX ? DownloadUXDialog : DownloadDialog; // TODO(cc) Download UI #5566 hidden under feature flag.

// Fetch the dataset assets on open of download modal.
const { datasetAssets, isError, isLoading } = useDatasetAssets(
Expand All @@ -44,35 +41,19 @@ const DownloadDataset: FC<Props> = ({
<>
<Button
datasetName={name}
disabled={isDisabled}
onClick={toggleOpen}
data-testid="dataset-download-button"
disabled={isDisabled}
onClick={() => setIsOpen(true)}
/>
<Modal
title="Download Dataset"
isCloseButtonShown={false}
isOpen={isOpen}
onClose={toggleOpen}
className={isLoading || isError ? "modal-loading" : undefined}
>
{isLoading ? (
<ModalContentWrapper>
<Spinner size={20} />
</ModalContentWrapper>
) : null}
{isError ? (
<ModalContentWrapper>
Dataset download is currently not available.
</ModalContentWrapper>
) : null}
{!isLoading && !isError ? (
<Content
name={name}
dataAssets={datasetAssets}
onClose={toggleOpen}
/>
) : null}
</Modal>
<Dialog onClose={() => setIsOpen(false)} open={isOpen}>
<Content
dataAssets={datasetAssets}
isError={isError}
isLoading={isLoading}
name={name}
onClose={() => setIsOpen(false)}
/>
</Dialog>
</>
);
};
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { FadeProps } from "@mui/material";

export interface Animation
extends Pick<FadeProps, "easing" | "in" | "timeout"> {
duration: number;
isCopying: boolean;
}

const ANIMATION_300_EASE_OUT = {
easing: "ease-out",
timeout: 250,
};

const ANIMATION_200_EASE_IN = {
easing: "ease-in",
timeout: 150,
};

// Possible animation step values.
export enum ANIMATION_STEP {
COPIED_ENTER = 2,
COPIED_EXIT = 3,
COPY_ENTER = 4,
COPY_EXIT = 1,
IDLE = 0,
}

// Animation properties for each step.
export const ANIMATION: Record<ANIMATION_STEP, Animation> = {
[ANIMATION_STEP.IDLE]: {
...ANIMATION_300_EASE_OUT,
in: true,
isCopying: false,
duration: 0,
},
[ANIMATION_STEP.COPY_EXIT]: {
...ANIMATION_300_EASE_OUT,
in: false,
isCopying: true,
duration: 0,
},
[ANIMATION_STEP.COPIED_ENTER]: {
...ANIMATION_300_EASE_OUT,
in: true,
isCopying: true,
duration: 1000, // Show "Copied" for 1 second.
},
[ANIMATION_STEP.COPIED_EXIT]: {
...ANIMATION_200_EASE_IN,
in: false,
isCopying: true,
duration: 0,
},
[ANIMATION_STEP.COPY_ENTER]: {
...ANIMATION_200_EASE_IN,
in: true,
isCopying: true,
duration: 0,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import copy from "clipboard-copy";
import { useEffect, useRef, useState } from "react";
import { CopyButton as Button } from "./style";
import { Fade } from "@mui/material";
import { ANIMATION, ANIMATION_STEP } from "./constants";

interface Props {
curl: string;
handleAnalytics: () => void;
}

export default function CopyButton({
curl,
handleAnalytics,
}: Props): JSX.Element {
const [animationStep, setAnimationStep] = useState<ANIMATION_STEP>(
ANIMATION_STEP.IDLE
);
const timeoutRef = useRef<NodeJS.Timer>();
const animation = ANIMATION[animationStep];

// Copy to clipboard, handle analytics, and initiate the copy animation.
const handleCopyClick = () => {
if (animation.isCopying) {
return; // Copying is in progress.
}
copy(curl);
handleAnalytics();
setAnimationStep(incrementAnimationState);
};

// Callback fired after the "exited" or "entered" status is applied.
// Executes while copy animation is in progress for "COPY_EXIT", "COPIED_ENTER" and "COPIED_EXIT", "COPY_ENTER".
// Increments the animation step.
const onUpdateAnimationStep = () => {
timeoutRef.current = setTimeout(() => {
// Executes the next animation progression, after duration of the current animation is complete.
setAnimationStep(incrementAnimationState);
}, animation.duration);
};

// Clears timeout when unmounting.
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);

return (
<Button
isAllCaps={false}
onClick={handleCopyClick}
sdsStyle="minimal"
sdsType="primary"
>
<Fade
appear={false}
easing={animation.easing}
in={animation.in}
onEntered={onUpdateAnimationStep}
onExited={onUpdateAnimationStep}
timeout={animation.timeout}
>
<span>{getButtonText(animationStep)}</span>
</Fade>
</Button>
);
}

/**
* Returns the button text to display.
* @param step - Current animation step.
* @returns button text.
*/
function getButtonText(step: number): string {
if (
step === ANIMATION_STEP.COPIED_ENTER ||
step === ANIMATION_STEP.COPIED_EXIT
) {
return "Copied";
}
return "Copy";
}

/**
* Increments the animation step.
* @param step - Current animation step.
* @returns next animation step.
*/
function incrementAnimationState(step: number): number {
if (step === ANIMATION_STEP.COPY_ENTER) {
return ANIMATION_STEP.IDLE;
}
return step + 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import styled from "@emotion/styled";
import { Button, fontBodyS } from "@czi-sds/components";

export const CopyButton = styled(Button)`
${fontBodyS}
font-weight: 500;
min-width: unset;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import copy from "clipboard-copy";
import { useState } from "react";
import { CopyMask as Mask } from "./style";

interface Props {
curl: string;
handleAnalytics: () => void;
}

/**
* @deprecated by CopyButton component once "DOWNLOAD_UX" feature flag is removed (#5566).
*/
export default function CopyMask({
curl,
handleAnalytics,
}: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<boolean>(false);

// Copy to clipboard, handle analytics.
const handleCopyClick = () => {
setIsCopied(true);
copy(curl);
handleAnalytics();
};

// Handle mouse enter event to reset the copy state.
const onMouseEnter = () => {
setIsCopied(false);
};

return (
<Mask onClick={handleCopyClick} onMouseEnter={onMouseEnter}>
{isCopied ? "Copied!" : "Copy to Clipboard"}
</Mask>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from "@emotion/styled";
import { fontBodyS } from "@czi-sds/components";

export const CopyMask = styled.div`
${fontBodyS}
align-items: center;
background-color: rgba(0, 118, 220, 0.9);
color: white;
cursor: pointer;
display: flex;
font-size: 16px;
height: 100%;
justify-content: center;
left: 0;
letter-spacing: normal;
line-height: inherit;
opacity: 0;
position: absolute;
top: 0;
width: 100%;
&:hover {
opacity: 1;
}
`;
Loading

0 comments on commit 7e3ccca

Please sign in to comment.