From ed34524940b0b742daba8195db0b3cbf25718c49 Mon Sep 17 00:00:00 2001 From: Brian Whitney Date: Fri, 8 Nov 2024 10:58:12 -0800 Subject: [PATCH 1/5] feature/move-files-styling --- .../CopyFileManifest.module.css} | 35 +++++++++-- .../index.tsx | 61 +++++++++++++------ packages/core/components/Modal/index.tsx | 8 +-- .../core/hooks/useFileAccessContextMenu.ts | 8 +-- packages/core/state/interaction/actions.ts | 22 +++---- packages/core/state/interaction/logics.ts | 16 ++--- packages/core/state/interaction/reducer.ts | 6 +- 7 files changed, 104 insertions(+), 52 deletions(-) rename packages/core/components/Modal/{MoveFileManifest/MoveFileManifest.module.css => CopyFileManifest/CopyFileManifest.module.css} (52%) rename packages/core/components/Modal/{MoveFileManifest => CopyFileManifest}/index.tsx (55%) diff --git a/packages/core/components/Modal/MoveFileManifest/MoveFileManifest.module.css b/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css similarity index 52% rename from packages/core/components/Modal/MoveFileManifest/MoveFileManifest.module.css rename to packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css index 641907b9..624ed4b1 100644 --- a/packages/core/components/Modal/MoveFileManifest/MoveFileManifest.module.css +++ b/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css @@ -1,9 +1,11 @@ +.bodyContainer { + position: relative; +} + .fileTableContainer { max-height: 300px; overflow-y: auto; - border: 1px solid var(--border-color); - margin-bottom: 1em; - padding: 1em; + margin-bottom: 0.5em; background: linear-gradient(to bottom, transparent, var(--secondary-background-color)); } @@ -16,7 +18,6 @@ .file-table td { padding: 8px; text-align: left; - border-bottom: 1px solid var(--border-color); white-space: nowrap; color: var(--primary-text-color); } @@ -31,3 +32,29 @@ .file-table td:first-child { border-right: 1px solid var(--border-color); } + +.footerButtons { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.summary { + display: flex; + justify-content: flex-end; + gap: 1em; + margin-top: var(--row-count-margin-top); + color: var(--secondary-text-color); + opacity: 0.8; + font-size: var(--row-count-intrisic-height); + height: var(--row-count-intrisic-height-height); +} + +.totalSize { + text-align: right; +} + +.fileCount { + text-align: right; +} + diff --git a/packages/core/components/Modal/MoveFileManifest/index.tsx b/packages/core/components/Modal/CopyFileManifest/index.tsx similarity index 55% rename from packages/core/components/Modal/MoveFileManifest/index.tsx rename to packages/core/components/Modal/CopyFileManifest/index.tsx index 41611686..bd29c960 100644 --- a/packages/core/components/Modal/MoveFileManifest/index.tsx +++ b/packages/core/components/Modal/CopyFileManifest/index.tsx @@ -4,17 +4,17 @@ import { useDispatch, useSelector } from "react-redux"; import { ModalProps } from ".."; import BaseModal from "../BaseModal"; -import { PrimaryButton } from "../../Buttons"; +import { PrimaryButton, SecondaryButton } from "../../Buttons"; import FileDetail from "../../../entity/FileDetail"; import FileSelection from "../../../entity/FileSelection"; import { interaction, selection } from "../../../state"; -import styles from "./MoveFileManifest.module.css"; +import styles from "./CopyFileManifest.module.css"; /** * Modal overlay for displaying details of selected files for NAS cache operations. */ -export default function MoveFileManifest({ onDismiss }: ModalProps) { +export default function CopyFileManifest({ onDismiss }: ModalProps) { const dispatch = useDispatch(); const fileService = useSelector(interaction.selectors.getFileService); const fileSelection = useSelector( @@ -26,6 +26,14 @@ export default function MoveFileManifest({ onDismiss }: ModalProps) { const [totalSize, setTotalSize] = React.useState(); const [isLoading, setLoading] = React.useState(false); + // Utility function to clip file names + const clipFileName = (filename: string) => { + if (filename.length > 20) { + return filename.slice(0, 9) + "..." + filename.slice(-8); + } + return filename; + }; + React.useEffect(() => { async function fetchDetails() { setLoading(true); @@ -42,13 +50,17 @@ export default function MoveFileManifest({ onDismiss }: ModalProps) { }, [fileSelection, fileService]); const onMove = () => { - dispatch(interaction.actions.moveFiles(fileDetails)); + dispatch(interaction.actions.copyFiles(fileDetails)); onDismiss(); }; const body = ( -
-

Selected Files:

+
+

+ Files copied to the local NAS cache (VAST) are stored with a 180-day lease, after + which they revert to cloud-only. To renew the lease, simply reselect the files and + confirm the copy. +

@@ -60,15 +72,21 @@ export default function MoveFileManifest({ onDismiss }: ModalProps) { {fileDetails.map((file) => ( - + ))}
{file.name}{clipFileName(file.name)} {filesize(file.size || 0)}
-

Total Files: {fileDetails.length}

-

Total Size: {isLoading ? "Loading..." : totalSize}

+
+ + {isLoading ? "Calculating..." : totalSize || "0 B"} + + + {fileDetails.length.toLocaleString()} files + +
); @@ -76,17 +94,24 @@ export default function MoveFileManifest({ onDismiss }: ModalProps) { +
+ + +
} onDismiss={onDismiss} - title="Move Files to NAS Cache" + title="Copy Files to NAS Cache (VAST)" /> ); } diff --git a/packages/core/components/Modal/index.tsx b/packages/core/components/Modal/index.tsx index 66f998f5..ac37e475 100644 --- a/packages/core/components/Modal/index.tsx +++ b/packages/core/components/Modal/index.tsx @@ -6,7 +6,7 @@ import CodeSnippet from "./CodeSnippet"; import DataSource from "./DataSource"; import MetadataManifest from "./MetadataManifest"; import SmallScreenWarning from "./SmallScreenWarning"; -import MoveFileManifest from "./MoveFileManifest"; +import CopyFileManifest from "./CopyFileManifest"; export interface ModalProps { onDismiss: () => void; @@ -17,7 +17,7 @@ export enum ModalType { DataSource = 2, MetadataManifest = 3, SmallScreenWarning = 4, - MoveFileManifest = 5, + CopyFileManifest = 5, } /** @@ -40,8 +40,8 @@ export default function Modal() { return ; case ModalType.SmallScreenWarning: return ; - case ModalType.MoveFileManifest: - return ; + case ModalType.CopyFileManifest: + return ; default: return null; } diff --git a/packages/core/hooks/useFileAccessContextMenu.ts b/packages/core/hooks/useFileAccessContextMenu.ts index 372c25e1..b4ed2682 100644 --- a/packages/core/hooks/useFileAccessContextMenu.ts +++ b/packages/core/hooks/useFileAccessContextMenu.ts @@ -138,13 +138,13 @@ export default (filters?: FileFilter[], onDismiss?: () => void) => { ...(isQueryingAicsFms ? [ { - key: "move-to-cache", - text: "Move to Cache", - title: "Move selected files to NAS Cache", + key: "copy-to-cache", + text: "Copy to vast", + title: "Copy selected files to NAS Cache (VAST)", disabled: !filters && fileSelection.count() === 0, iconProps: { iconName: "MoveToFolder" }, onClick() { - dispatch(interaction.actions.showMoveFileManifest()); + dispatch(interaction.actions.showCopyFileManifest()); }, }, ] diff --git a/packages/core/state/interaction/actions.ts b/packages/core/state/interaction/actions.ts index 7f85149c..2d9a0b9d 100644 --- a/packages/core/state/interaction/actions.ts +++ b/packages/core/state/interaction/actions.ts @@ -681,35 +681,35 @@ export function setSelectedPublicDataset(dataset: PublicDataset): SetSelectedPub } /** - * SHOW_MOVE_FILE_MANIFEST + * SHOW_COPY_FILE_MANIFEST * - * Action to show the Move File dialog (manifest) for NAS cache operations. - * This modal will allow users to move files onto the NAS cache. + * Action to show the Copy File dialog (manifest) for NAS cache operations. + * This modal will allow users to copy files onto the NAS cache. */ -export const SHOW_MOVE_FILE_MANIFEST = makeConstant(STATE_BRANCH_NAME, "show-move-file-manifest"); +export const SHOW_COPY_FILE_MANIFEST = makeConstant(STATE_BRANCH_NAME, "show-copy-file-manifest"); -export interface ShowMoveFileManifestAction { +export interface ShowCopyFileManifestAction { type: string; } -export function showMoveFileManifest(): ShowMoveFileManifestAction { +export function showCopyFileManifest(): ShowCopyFileManifestAction { return { - type: SHOW_MOVE_FILE_MANIFEST, + type: SHOW_COPY_FILE_MANIFEST, }; } -export const MOVE_FILES = makeConstant(STATE_BRANCH_NAME, "move-files"); +export const COPY_FILES = makeConstant(STATE_BRANCH_NAME, "copy-files"); -export interface MoveFilesAction { +export interface CopyFilesAction { type: string; payload: { fileDetails: FileDetail[]; }; } -export function moveFiles(fileDetails: FileDetail[]): MoveFilesAction { +export function copyFiles(fileDetails: FileDetail[]): CopyFilesAction { return { - type: MOVE_FILES, + type: COPY_FILES, payload: { fileDetails, }, diff --git a/packages/core/state/interaction/logics.ts b/packages/core/state/interaction/logics.ts index 3e70a360..eeed34ed 100644 --- a/packages/core/state/interaction/logics.ts +++ b/packages/core/state/interaction/logics.ts @@ -31,8 +31,8 @@ import { SetIsSmallScreenAction, setVisibleModal, hideVisibleModal, - MoveFilesAction, - MOVE_FILES, + CopyFilesAction, + COPY_FILES, } from "./actions"; import * as interactionSelectors from "./selectors"; import { DownloadResolution, FileInfo } from "../../services/FileDownloadService"; @@ -578,13 +578,13 @@ const setIsSmallScreen = createLogic({ }); /** - * Interceptor responsible for handling the MOVE_FILES action. - * Logs details of files that are being moved. + * Interceptor responsible for handling the COPY_FILES action. + * Logs details of files that are being copied to cache. */ -const moveFilesLogic = createLogic({ - type: MOVE_FILES, +const CopyFilesLogic = createLogic({ + type: COPY_FILES, process(deps, dispatch, done) { - const action = deps.action as MoveFilesAction; + const action = deps.action as CopyFilesAction; console.log(`Moving files:`, action.payload.fileDetails); done(); }, @@ -601,5 +601,5 @@ export default [ showContextMenu, refresh, setIsSmallScreen, - moveFilesLogic, + CopyFilesLogic, ]; diff --git a/packages/core/state/interaction/reducer.ts b/packages/core/state/interaction/reducer.ts index 2f39ea7e..93b457f0 100644 --- a/packages/core/state/interaction/reducer.ts +++ b/packages/core/state/interaction/reducer.ts @@ -15,7 +15,7 @@ import { SHOW_CONTEXT_MENU, SHOW_DATASET_DETAILS_PANEL, SHOW_MANIFEST_DOWNLOAD_DIALOG, - SHOW_MOVE_FILE_MANIFEST, + SHOW_COPY_FILE_MANIFEST, StatusUpdate, MARK_AS_USED_APPLICATION_BEFORE, MARK_AS_DISMISSED_SMALL_SCREEN_WARNING, @@ -196,9 +196,9 @@ export default makeReducer( ...state, selectedPublicDataset: action.payload, }), - [SHOW_MOVE_FILE_MANIFEST]: (state) => ({ + [SHOW_COPY_FILE_MANIFEST]: (state) => ({ ...state, - visibleModal: ModalType.MoveFileManifest, + visibleModal: ModalType.CopyFileManifest, }), }, initialState From 7e94c3c04866950986edbd8e892d0527cef2d717 Mon Sep 17 00:00:00 2001 From: Brian Whitney Date: Wed, 18 Dec 2024 12:27:14 -0800 Subject: [PATCH 2/5] seperate table --- .../Modal/CopyFileManifest/index.tsx | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/core/components/Modal/CopyFileManifest/index.tsx b/packages/core/components/Modal/CopyFileManifest/index.tsx index bd29c960..3d998acc 100644 --- a/packages/core/components/Modal/CopyFileManifest/index.tsx +++ b/packages/core/components/Modal/CopyFileManifest/index.tsx @@ -49,18 +49,31 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) { fetchDetails(); }, [fileSelection, fileService]); + // Handler for moving files to NAS cache const onMove = () => { dispatch(interaction.actions.copyFiles(fileDetails)); onDismiss(); }; - const body = ( -
-

- Files copied to the local NAS cache (VAST) are stored with a 180-day lease, after - which they revert to cloud-only. To renew the lease, simply reselect the files and - confirm the copy. -

+ // Separate files by "Should Be in Local Cache" + const filesInLocalCache = fileDetails.filter((file) => + file.annotations.some( + (annotation) => + annotation.name === "Should Be in Local Cache" && annotation.values[0] === true + ) + ); + + const filesNotInLocalCache = fileDetails.filter((file) => + file.annotations.some( + (annotation) => + annotation.name === "Should Be in Local Cache" && annotation.values[0] === false + ) + ); + + // Reusable function to render a table for files + const renderTable = (files: FileDetail[], title: string) => ( +
+

{title}

@@ -70,7 +83,7 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) { - {fileDetails.map((file) => ( + {files.map((file) => ( @@ -79,6 +92,21 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) {
{clipFileName(file.name)} {filesize(file.size || 0)}
+
+ ); + + const body = ( +
+

+ Files copied to the local NAS cache (VAST) are stored with a 180-day lease, after + which they revert to cloud-only. To renew the lease, simply reselect the files and + confirm the copy. +

+ {renderTable( + filesInLocalCache, + "Files that are already in Local Cache (VAST) to renew lease for" + )} + {renderTable(filesNotInLocalCache, "Files to Download to Local Cache (VAST)")}
{isLoading ? "Calculating..." : totalSize || "0 B"} From a94c95ec654332708ddf91c18015248ce4ad3f80 Mon Sep 17 00:00:00 2001 From: Brian Whitney Date: Wed, 18 Dec 2024 12:48:38 -0800 Subject: [PATCH 3/5] update wording --- .../components/Modal/CopyFileManifest/index.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/core/components/Modal/CopyFileManifest/index.tsx b/packages/core/components/Modal/CopyFileManifest/index.tsx index 3d998acc..3e8b345f 100644 --- a/packages/core/components/Modal/CopyFileManifest/index.tsx +++ b/packages/core/components/Modal/CopyFileManifest/index.tsx @@ -98,15 +98,12 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) { const body = (

- Files copied to the local NAS cache (VAST) are stored with a 180-day lease, after - which they revert to cloud-only. To renew the lease, simply reselect the files and - confirm the copy. + Files copied to the local NAS (Vast) are stored with a 180-day expiration, after + which they revert to cloud-only. To extend the expiration, simply reselect the files + and confirm the update.

- {renderTable( - filesInLocalCache, - "Files that are already in Local Cache (VAST) to renew lease for" - )} - {renderTable(filesNotInLocalCache, "Files to Download to Local Cache (VAST)")} + {renderTable(filesInLocalCache, "Files that are already on Vast: Extend expiration")} + {renderTable(filesNotInLocalCache, "Files to download to Vast")}
{isLoading ? "Calculating..." : totalSize || "0 B"} @@ -139,7 +136,7 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) {
} onDismiss={onDismiss} - title="Copy Files to NAS Cache (VAST)" + title="Copy Files to Local NAS (Vast)" /> ); } From 1642a6858a92b1ab25e81c814451ca47252a7f58 Mon Sep 17 00:00:00 2001 From: Brian Whitney Date: Thu, 19 Dec 2024 15:54:00 -0800 Subject: [PATCH 4/5] update table gradient and summeries --- .../CopyFileManifest.module.css | 41 +++- .../Modal/CopyFileManifest/index.tsx | 178 ++++++++++-------- 2 files changed, 136 insertions(+), 83 deletions(-) diff --git a/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css b/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css index 624ed4b1..48482b9b 100644 --- a/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css +++ b/packages/core/components/Modal/CopyFileManifest/CopyFileManifest.module.css @@ -2,16 +2,49 @@ position: relative; } +.note { + margin-top: 0.5em; + margin-bottom: 1em; +} + +.tableContainer { + margin-bottom: 2em; +} + +.tableTitle { + font-size: 16px; + font-weight: 600; + margin: 4px 0 8px 0; +} + +.tableWrapper { + position: relative; +} + .fileTableContainer { max-height: 300px; overflow-y: auto; - margin-bottom: 0.5em; - background: linear-gradient(to bottom, transparent, var(--secondary-background-color)); + background-color: var(--secondary-background-color); + position: relative; + z-index: 1; +} + +.gradientOverlay { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 50px; + background-image: linear-gradient(transparent, black); + pointer-events: none; + z-index: 2; } .file-table { width: 100%; border-collapse: collapse; + position: relative; + z-index: 1; } .file-table th, @@ -20,13 +53,14 @@ text-align: left; white-space: nowrap; color: var(--primary-text-color); + background-color: var(--secondary-color); } .file-table th { background-color: var(--secondary-background-color); position: sticky; top: 0; - z-index: 1; + z-index: 3; } .file-table td:first-child { @@ -57,4 +91,3 @@ .fileCount { text-align: right; } - diff --git a/packages/core/components/Modal/CopyFileManifest/index.tsx b/packages/core/components/Modal/CopyFileManifest/index.tsx index 3e8b345f..c18e1bd8 100644 --- a/packages/core/components/Modal/CopyFileManifest/index.tsx +++ b/packages/core/components/Modal/CopyFileManifest/index.tsx @@ -11,51 +11,101 @@ import { interaction, selection } from "../../../state"; import styles from "./CopyFileManifest.module.css"; +/** + * Table component for rendering file details. + */ +function FileTable({ files, title }: { files: FileDetail[]; title: string }) { + const containerRef = React.useRef(null); + const [hasScroll, setHasScroll] = React.useState(false); + + React.useEffect(() => { + const checkScroll = () => { + if (containerRef.current) { + const isScrollable = + containerRef.current.scrollHeight > containerRef.current.clientHeight; + setHasScroll(isScrollable); + } + }; + checkScroll(); // Initial check + window.addEventListener("resize", checkScroll); + return () => window.removeEventListener("resize", checkScroll); + }, [files]); + + const clipFileName = (filename: string) => { + if (filename.length > 20) { + return filename.slice(0, 9) + "..." + filename.slice(-8); + } + return filename; + }; + + const calculateTotalSize = (files: FileDetail[]) => { + if (files.length === 0) return ""; + const totalBytes = files.reduce((acc, file) => acc + (file.size || 0), 0); + return totalBytes ? filesize(totalBytes) : "Calculating..."; + }; + + return ( +
+

{title}

+
+
+ + + + + + + + + {files.map((file) => ( + + + + + ))} + +
File NameFile Size
{clipFileName(file.name)}{filesize(file.size || 0)}
+
+ {hasScroll &&
} +
+
+ {files.length > 0 && ( + {calculateTotalSize(files)} + )} + {files.length.toLocaleString()} files +
+
+ ); +} + /** * Modal overlay for displaying details of selected files for NAS cache operations. */ export default function CopyFileManifest({ onDismiss }: ModalProps) { const dispatch = useDispatch(); - const fileService = useSelector(interaction.selectors.getFileService); const fileSelection = useSelector( selection.selectors.getFileSelection, FileSelection.selectionsAreEqual ); const [fileDetails, setFileDetails] = React.useState([]); - const [totalSize, setTotalSize] = React.useState(); - const [isLoading, setLoading] = React.useState(false); - - // Utility function to clip file names - const clipFileName = (filename: string) => { - if (filename.length > 20) { - return filename.slice(0, 9) + "..." + filename.slice(-8); - } - return filename; - }; React.useEffect(() => { async function fetchDetails() { - setLoading(true); const details = await fileSelection.fetchAllDetails(); setFileDetails(details); - - const aggregateInfo = await fileService.getAggregateInformation(fileSelection); - const formattedSize = aggregateInfo.size ? filesize(aggregateInfo.size) : undefined; - setTotalSize(formattedSize); - setLoading(false); } - fetchDetails(); - }, [fileSelection, fileService]); + }, [fileSelection]); - // Handler for moving files to NAS cache const onMove = () => { dispatch(interaction.actions.copyFiles(fileDetails)); onDismiss(); }; - // Separate files by "Should Be in Local Cache" const filesInLocalCache = fileDetails.filter((file) => file.annotations.some( (annotation) => @@ -63,63 +113,39 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) { ) ); - const filesNotInLocalCache = fileDetails.filter((file) => - file.annotations.some( - (annotation) => - annotation.name === "Should Be in Local Cache" && annotation.values[0] === false - ) - ); - - // Reusable function to render a table for files - const renderTable = (files: FileDetail[], title: string) => ( -
-

{title}

-
- - - - - - - - - {files.map((file) => ( - - - - - ))} - -
File NameFile Size
{clipFileName(file.name)}{filesize(file.size || 0)}
-
-
- ); - - const body = ( -
-

- Files copied to the local NAS (Vast) are stored with a 180-day expiration, after - which they revert to cloud-only. To extend the expiration, simply reselect the files - and confirm the update. -

- {renderTable(filesInLocalCache, "Files that are already on Vast: Extend expiration")} - {renderTable(filesNotInLocalCache, "Files to download to Vast")} -
- - {isLoading ? "Calculating..." : totalSize || "0 B"} - - - {fileDetails.length.toLocaleString()} files - -
-
+ const filesNotInLocalCache = fileDetails.filter( + (file) => + file.annotations.some( + (annotation) => + annotation.name === "Should Be in Local Cache" && annotation.values[0] === false + ) || + !file.annotations.some((annotation) => annotation.name === "Should Be in Local Cache") ); return ( +

+ Files copied to the local NAS (VAST) are stored with a 180-day expiration, + after which they revert to cloud-only storage. To extend the expiration, + reselect the files and confirm the update. +

+ + +
+ } footer={
+ -
} onDismiss={onDismiss} - title="Copy Files to Local NAS (Vast)" + title="Copy files to local NAS (VAST)" /> ); } From efccc6c78bd5038685b43fbb9713721bfde8b374 Mon Sep 17 00:00:00 2001 From: Brian Whitney Date: Fri, 20 Dec 2024 11:14:30 -0800 Subject: [PATCH 5/5] remove NAS wording --- .../core/components/Modal/CopyFileManifest/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/components/Modal/CopyFileManifest/index.tsx b/packages/core/components/Modal/CopyFileManifest/index.tsx index c18e1bd8..8deeecd2 100644 --- a/packages/core/components/Modal/CopyFileManifest/index.tsx +++ b/packages/core/components/Modal/CopyFileManifest/index.tsx @@ -82,7 +82,7 @@ function FileTable({ files, title }: { files: FileDetail[]; title: string }) { } /** - * Modal overlay for displaying details of selected files for NAS cache operations. + * Modal overlay for displaying details of selected files for NAS cache (VAST) operations. */ export default function CopyFileManifest({ onDismiss }: ModalProps) { const dispatch = useDispatch(); @@ -127,9 +127,9 @@ export default function CopyFileManifest({ onDismiss }: ModalProps) { body={

- Files copied to the local NAS (VAST) are stored with a 180-day expiration, - after which they revert to cloud-only storage. To extend the expiration, - reselect the files and confirm the update. + Files copied to the local storage (VAST) are stored with a 180-day + expiration, after which they revert to cloud-only storage. To extend the + expiration, reselect the files and confirm the update.

} onDismiss={onDismiss} - title="Copy files to local NAS (VAST)" + title="Copy files to local storage (VAST)" /> ); }