From 79c0d5fe39de4a926b10cd5ce3ca63028b6461b3 Mon Sep 17 00:00:00 2001 From: mariana-furyk Date: Tue, 24 Oct 2023 13:00:57 +0300 Subject: [PATCH] Impl [Download] Revamp Download by Figma --- src/common/Download/Download.js | 223 ++++-------------- ...ownloadWrapper.js => DownloadContainer.js} | 12 +- src/common/Download/DownloadItem.js | 25 +- src/common/Download/download.scss | 56 +---- ...oadWrapper.scss => downloadContainer.scss} | 14 +- src/components/Datasets/Datasets.js | 15 ++ .../Details/DetailsHeader/DetailsHeader.js | 20 +- .../DetailsArtifacts/DetailsArtifactsView.js | 1 + src/components/Files/Files.js | 15 ++ src/components/ModelsPage/Models/Models.js | 15 ++ src/elements/PreviewModal/PreviewModal.js | 11 +- src/elements/TableCell/TableCell.js | 13 +- src/layout/Page/Page.js | 2 + src/utils/createArtifactsContent.js | 27 --- 14 files changed, 147 insertions(+), 302 deletions(-) rename src/common/Download/{DownloadWrapper.js => DownloadContainer.js} (88%) rename src/common/Download/{downloadWrapper.scss => downloadContainer.scss} (89%) diff --git a/src/common/Download/Download.js b/src/common/Download/Download.js index 4cefa9da6b..5f91e4504e 100644 --- a/src/common/Download/Download.js +++ b/src/common/Download/Download.js @@ -17,202 +17,73 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useState, useEffect, useCallback, useRef } from 'react' +import React, { useRef } from 'react' import PropTypes from 'prop-types' -import axios from 'axios' -import classnames from 'classnames' import { useDispatch } from 'react-redux' -import { useParams } from 'react-router-dom' +import classnames from 'classnames' -import ProgressRing from '../ProgressRing/ProgressRing' +import { RoundedIcon, Tooltip, TextTooltipTemplate } from 'igz-controls/components' -import { mainHttpClient } from '../../httpClient' -import { setNotification } from '../../reducers/notificationReducer' -import downloadFile from '../../utils/downloadFile' +import { setDownloadItem } from '../../reducers/downloadReducer' -import './download.scss' -import colors from 'igz-controls/scss/colors.scss' +import { ReactComponent as DownloadIcon } from '../../../../dashboard-react-controls/src/lib/images/download.svg' -const DEFAULT_FILE_NAME = 'mlrun-file' - -const Download = ({ disabled, fileName, path, user }) => { - const [progress, setProgress] = useState(0) - const [isDownload, setDownload] = useState(false) - const params = useParams() - const downloadContainerClassnames = classnames( - 'download-container', - disabled && 'download-container_disabled' - ) +import './download.scss' +const Download = ({ className, disabled, fileName, onlyIcon, path, user, withoutIcon }) => { const downloadRef = useRef(null) const dispatch = useDispatch() - - const progressRingRadius = '20' - const progressRingStroke = '3' - - let file = fileName ?? path.match(/\/(?[^/]+)$/)?.groups?.filename ?? DEFAULT_FILE_NAME - - const retryDownload = useCallback( - item => { - mainHttpClient - .get(item.url) - .then(response => { - downloadFile(item.file, response) - dispatch( - setNotification({ - status: response.status, - url: response.config.url, - id: Math.random(), - message: 'Downloaded successfully' - }) - ) - }) - .catch(() => { - dispatch( - setNotification({ - status: 400, - url: item.url, - file: item.file, - id: Math.random(), - message: 'Failed to download' - }) - ) - }) - }, - [dispatch] - ) - - const downloadCallback = useCallback(() => { - if (isDownload) { - const config = { - onDownloadProgress: progressEvent => { - const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total - setProgress(percentCompleted) - }, - cancelToken: new axios.CancelToken(cancel => { - downloadRef.current.cancel = cancel - }), - params: { path }, - responseType: 'arraybuffer' - } - - if (path.startsWith('/User')) { - config.params.user = user - } - - mainHttpClient - .get(`projects/${params.projectName}/files`, config) - .then(response => { - downloadFile(file, response) - dispatch( - setNotification({ - status: response.status, - url: response.config.url, - id: Math.random(), - message: 'Downloaded successfully' - }) - ) - if (downloadRef.current) { - setDownload(false) - setProgress(0) - } - }) - .catch(error => { - if (axios.isCancel(error)) { - setDownload(false) - return setProgress(0) - } - dispatch( - setNotification({ - status: 400, - url: `/files?path=${path}&user=${user}`, - file, - id: Math.random(), - retry: item => retryDownload(item), - message: 'Failed to download' - }) - ) - if (downloadRef.current) { - setDownload(false) - setProgress(0) - } - }) - .finally(() => { - if (downloadRef.current) downloadRef.current.cancel = null - }) - } - }, [isDownload, path, params.projectName, user, file, dispatch, retryDownload]) - - useEffect(() => { - let cancelFetch = downloadRef.current - - downloadCallback() - - return () => { - cancelFetch.cancel && cancelFetch.cancel() - } - }, [downloadCallback, downloadRef]) + const downloadClassNames = classnames('download', className, disabled && 'download_disabled') const handleClick = () => { - if (!disabled) { - if (downloadRef.current?.cancel) { - return downloadRef.current.cancel('cancel') - } - - setDownload(!isDownload) - } + dispatch( + setDownloadItem({ + filename: fileName, + id: path + Date.now(), + path, + user: user + }) + ) } return ( -
- - - - {!isDownload ? ( - - - - - - - ) : ( - - - - - )} - - +
+ {onlyIcon ? ( + }> + + + + + ) : ( + <> +
Download
+ {!withoutIcon && } + + )}
) } +Download.defaultProps = { + className: '', + disabled: false, + onlyIcon: false, + withoutIcon: false +} + Download.propTypes = { + className: PropTypes.string, + disabled: PropTypes.bool, fileName: PropTypes.string, - path: PropTypes.string.isRequired + onlyIcon: PropTypes.bool, + path: PropTypes.string.isRequired, + user: PropTypes.string, + withoutIcon: PropTypes.bool } export default React.memo(Download) diff --git a/src/common/Download/DownloadWrapper.js b/src/common/Download/DownloadContainer.js similarity index 88% rename from src/common/Download/DownloadWrapper.js rename to src/common/Download/DownloadContainer.js index 9116131695..cf714d650c 100644 --- a/src/common/Download/DownloadWrapper.js +++ b/src/common/Download/DownloadContainer.js @@ -24,9 +24,9 @@ import { Transition, TransitionGroup } from 'react-transition-group' import DownloadItem from './DownloadItem' -import './donwloadWrapper.scss' +import './downloadContainer.scss' -const DownloadWrapper = () => { +const DownloadContainer = () => { const downloadStore = useSelector(store => store.downloadStore) const duration = 500 @@ -49,7 +49,11 @@ const DownloadWrapper = () => { {state => createPortal( -
+
Downloads
{downloadStore.downloadList.map((downloadItem, index) => { @@ -66,4 +70,4 @@ const DownloadWrapper = () => { ) } -export default DownloadWrapper +export default DownloadContainer diff --git a/src/common/Download/DownloadItem.js b/src/common/Download/DownloadItem.js index 0ae354b80d..c474e30c55 100644 --- a/src/common/Download/DownloadItem.js +++ b/src/common/Download/DownloadItem.js @@ -41,6 +41,7 @@ const DownloadItem = ({ downloadItem }) => { const params = useParams() const downloadRef = useRef(null) + const timeoutRef = useRef(null) const dispatch = useDispatch() let file = useMemo( @@ -85,6 +86,7 @@ const DownloadItem = ({ downloadItem }) => { setDownload(false) return setProgress(0) } + if (downloadRef.current) { setDownload(false) setProgress(0) @@ -94,7 +96,8 @@ const DownloadItem = ({ downloadItem }) => { if (downloadRef.current) { downloadRef.current.cancel = null } - setTimeout(() => { + + timeoutRef.current = setTimeout(() => { dispatch(removeDownloadItem(downloadItem.id)) }, 1000) }) @@ -121,7 +124,7 @@ const DownloadItem = ({ downloadItem }) => { } }, [downloadCallback, downloadRef]) - const handleClick = () => { + const handleCancel = () => { if (downloadRef.current?.cancel) { return downloadRef.current.cancel('cancel') } @@ -129,8 +132,16 @@ const DownloadItem = ({ downloadItem }) => { setDownload(!isDownload) } + const handleRetry = () => { + setDownload(true) + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + return ( -
+
}> {file} @@ -145,15 +156,11 @@ const DownloadItem = ({ downloadItem }) => {
{isDownload ? ( - + ) : !isSuccessResponse ? ( - { - setDownload(true) - }} - > + ) : null} diff --git a/src/common/Download/download.scss b/src/common/Download/download.scss index 42d1e30df6..058cead1a3 100644 --- a/src/common/Download/download.scss +++ b/src/common/Download/download.scss @@ -1,53 +1,17 @@ @import '~igz-controls/scss/colors'; -@import '~igz-controls/scss/shadows'; -.download-container { - &:not(.download-container_disabled) { - .download { - cursor: pointer; +.download { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; - &:hover { - fill: $mulledWineThree; - } - } + &_disabled { + color: $topaz; + cursor: not-allowed; } - &.download-container_disabled { - .download-container__icon { - rect { - fill: $spunPearl; - } - } - } - - .download { - fill: transparent; - } - - .downloading { - cursor: pointer; - fill: $malibu; - - &:hover { - fill: $cornflowerBlue; - } - } - - .download-container__icon { - transform: translate(8px, 8px); - - rect { - fill: $topaz; - //width: 9.05318px; - //height: 1.50886px; - } - } - - .cancel_container { - transform: translate(14px, 14px); - - rect { - fill: $white; - } + &__label { + margin-right: 10px; } } diff --git a/src/common/Download/downloadWrapper.scss b/src/common/Download/downloadContainer.scss similarity index 89% rename from src/common/Download/downloadWrapper.scss rename to src/common/Download/downloadContainer.scss index c5122d8bf3..9d70edb113 100644 --- a/src/common/Download/downloadWrapper.scss +++ b/src/common/Download/downloadContainer.scss @@ -23,6 +23,8 @@ &__body { display: flex; flex-direction: column; + max-height: 140px; + overflow-y: scroll; .download-item { display: flex; @@ -31,6 +33,10 @@ margin-bottom: 10px; font-size: 12px; + &__filename { + margin-right: 5px; + } + &__message { &_succeed { color: $brightTurquoise; @@ -51,12 +57,4 @@ } } } - - .downloading { - cursor: pointer; - } - - .cancel_container { - transform: translate(14px, 14px); - } } diff --git a/src/components/Datasets/Datasets.js b/src/components/Datasets/Datasets.js index 2380bfe341..e0289743e0 100644 --- a/src/components/Datasets/Datasets.js +++ b/src/components/Datasets/Datasets.js @@ -61,11 +61,13 @@ import { useYaml } from '../../hooks/yaml.hook' import { getViewMode } from '../../utils/helper' import { copyToClipboard } from '../../utils/copyToClipboard' import { generateUri } from '../../utils/resources' +import { setDownloadItem } from '../../reducers/downloadReducer' import { ReactComponent as TagIcon } from 'igz-controls/images/tag-icon.svg' import { ReactComponent as YamlIcon } from 'igz-controls/images/yaml.svg' import { ReactComponent as ArtifactView } from 'igz-controls/images/eye-icon.svg' import { ReactComponent as Copy } from 'igz-controls/images/copy-to-clipboard-icon.svg' +import { ReactComponent as DownloadIcon } from '../../../../dashboard-react-controls/src/lib/images/download.svg' const Datasets = () => { const [datasets, setDatasets] = useState([]) @@ -151,9 +153,22 @@ const Datasets = () => { const actionsMenu = useMemo( () => dataset => { const isTargetPathValid = getIsTargetPathValid(dataset ?? {}, frontendSpec) + const downloadPath = `${dataset?.target_path}${dataset?.model_file || ''}` return [ [ + { + label: 'Download', + icon: , + onClick: dataset => + dispatch( + setDownloadItem({ + path: downloadPath, + user: dataset.producer?.owner, + id: downloadPath + }) + ) + }, { label: 'Copy URI', icon: , diff --git a/src/components/Details/DetailsHeader/DetailsHeader.js b/src/components/Details/DetailsHeader/DetailsHeader.js index 408004bbaa..cade010a70 100644 --- a/src/components/Details/DetailsHeader/DetailsHeader.js +++ b/src/components/Details/DetailsHeader/DetailsHeader.js @@ -26,18 +26,9 @@ import { useSelector } from 'react-redux' import { Button, Tooltip, TextTooltipTemplate, RoundedIcon } from 'igz-controls/components' import LoadButton from '../../../common/LoadButton/LoadButton' import Select from '../../../common/Select/Select' -import Download from '../../../common/Download/Download' import ActionsMenu from '../../../common/ActionsMenu/ActionsMenu' -import { - DATASETS_PAGE, - DETAILS_ARTIFACTS_TAB, - FILES_PAGE, - FULL_VIEW_MODE, - JOBS_PAGE, - MODEL_ENDPOINTS_TAB, - MODELS_PAGE -} from '../../../constants' +import { DETAILS_ARTIFACTS_TAB, FULL_VIEW_MODE, JOBS_PAGE } from '../../../constants' import { formatDatetime } from '../../../utils' import { LABEL_BUTTON } from 'igz-controls/constants' import { ACTIONS_MENU } from '../../../types' @@ -212,15 +203,6 @@ const DetailsHeader = ({ selectedId={detailsStore.iteration} /> )} - {[FILES_PAGE, DATASETS_PAGE, MODELS_PAGE].includes(pageData.page) && - pageData.details.type !== MODEL_ENDPOINTS_TAB && ( - }> - - - )} {actionButton && !actionButton.hidden && (
diff --git a/src/elements/TableCell/TableCell.js b/src/elements/TableCell/TableCell.js index 5d70f1ba07..60c4b5c1ab 100644 --- a/src/elements/TableCell/TableCell.js +++ b/src/elements/TableCell/TableCell.js @@ -142,13 +142,12 @@ const TableCell = ({ } else if (data.type === 'buttonDownload') { return ( - + ) } else if (data.type === BUTTON_COPY_URI_CELL_TYPE) { diff --git a/src/layout/Page/Page.js b/src/layout/Page/Page.js index 8d624ecc35..55fa0335cd 100644 --- a/src/layout/Page/Page.js +++ b/src/layout/Page/Page.js @@ -24,6 +24,7 @@ import classNames from 'classnames' import { isEmpty } from 'lodash' import Notification from '../../common/Notification/Notification' +import DownloadContainer from '../../common/Download/DownloadContainer' import { getTransitionEndEventName } from 'igz-controls/utils/common.util' import { fetchFrontendSpec } from '../../reducers/appReducer' @@ -84,6 +85,7 @@ const Page = ({ isNavbarPinned, setProjectName }) => {
+ ) } diff --git a/src/utils/createArtifactsContent.js b/src/utils/createArtifactsContent.js index bcf8391339..32e440cad8 100644 --- a/src/utils/createArtifactsContent.js +++ b/src/utils/createArtifactsContent.js @@ -218,15 +218,6 @@ export const createModelsRowData = (artifact, project, frontendSpec, showExpandB class: 'table-cell-1', type: 'hidden' } - //TODO: remove when new Download will be added - // { - // id: `buttonDownload.${artifact.ui.identifierUnique}`, - // headerId: 'download', - // value: '', - // class: 'table-cell-icon', - // type: 'buttonDownload', - // disabled: !isTargetPathValid - // } ] } } @@ -323,15 +314,6 @@ export const createFilesRowData = (artifact, project, frontendSpec, showExpandBu value: isNumber(artifact.size) && artifact.size >= 0 ? convertBytes(artifact.size) : 'N/A', class: 'table-cell-1' } - //TODO: remove when new Download will be added - // { - // id: `buttonDownload.${artifact.ui.identifierUnique}`, - // headerId: 'download', - // value: '', - // class: 'table-cell-icon', - // type: 'buttonDownload', - // disabled: !isTargetPathValid - // }, ] } } @@ -548,15 +530,6 @@ export const createDatasetsRowData = (artifact, project, frontendSpec, showExpan class: 'table-cell-1', type: 'hidden' } - //TODO: remove when new Download will be added - // { - // id: `buttonDownload.${artifact.ui.identifierUnique}`, - // headerId: 'download', - // value: '', - // class: 'table-cell-icon', - // type: 'buttonDownload', - // disabled: !isTargetPathValid - // } ] } }