Skip to content

Commit

Permalink
update table gradient and summeries
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianWhitneyAI committed Dec 19, 2024
1 parent a94c95e commit 1642a68
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -57,4 +91,3 @@
.fileCount {
text-align: right;
}

178 changes: 99 additions & 79 deletions packages/core/components/Modal/CopyFileManifest/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,132 +11,152 @@ 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<HTMLDivElement>(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 (
<div className={styles.tableContainer}>
<h3 className={styles.tableTitle}>{title}</h3>
<div className={styles.tableWrapper}>
<div
ref={containerRef}
className={`${styles.fileTableContainer} ${hasScroll ? styles.hasScroll : ""}`}
>
<table className={styles.fileTable}>
<thead>
<tr>
<th>File Name</th>
<th>File Size</th>
</tr>
</thead>
<tbody>
{files.map((file) => (
<tr key={file.id}>
<td>{clipFileName(file.name)}</td>
<td>{filesize(file.size || 0)}</td>
</tr>
))}
</tbody>
</table>
</div>
{hasScroll && <div className={styles.gradientOverlay} />}
</div>
<div className={styles.summary}>
{files.length > 0 && (
<span className={styles.totalSize}>{calculateTotalSize(files)}</span>
)}
<span className={styles.fileCount}>{files.length.toLocaleString()} files</span>
</div>
</div>
);
}

/**
* 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<FileDetail[]>([]);
const [totalSize, setTotalSize] = React.useState<string | undefined>();
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) =>
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) => (
<div>
<h3 className={styles.tableTitle}>{title}</h3>
<div className={styles.fileTableContainer}>
<table className={styles.fileTable}>
<thead>
<tr>
<th>File Name</th>
<th>File Size</th>
</tr>
</thead>
<tbody>
{files.map((file) => (
<tr key={file.id}>
<td>{clipFileName(file.name)}</td>
<td>{filesize(file.size || 0)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);

const body = (
<div className={styles.bodyContainer}>
<p className={styles.note}>
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.
</p>
{renderTable(filesInLocalCache, "Files that are already on Vast: Extend expiration")}
{renderTable(filesNotInLocalCache, "Files to download to Vast")}
<div className={styles.summary}>
<span className={styles.totalSize}>
{isLoading ? "Calculating..." : totalSize || "0 B"}
</span>
<span className={styles.fileCount}>
{fileDetails.length.toLocaleString()} files
</span>
</div>
</div>
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 (
<BaseModal
body={body}
body={
<div className={styles.bodyContainer}>
<p className={styles.note}>
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.
</p>
<FileTable
files={filesInLocalCache}
title="Files that are already on VAST: Extend expiration"
/>
<FileTable files={filesNotInLocalCache} title="Files to download to VAST" />
</div>
}
footer={
<div className={styles.footerButtons}>
<SecondaryButton
className={styles.cancelButton}
onClick={onDismiss}
text="CANCEL"
title=""
/>
<PrimaryButton
className={styles.confirmButton}
disabled={!fileDetails.length}
onClick={onMove}
text="CONFIRM"
title=""
/>
<SecondaryButton
className={styles.cancelButton}
onClick={onDismiss}
text="CANCEL"
title=""
/>
</div>
}
onDismiss={onDismiss}
title="Copy Files to Local NAS (Vast)"
title="Copy files to local NAS (VAST)"
/>
);
}

0 comments on commit 1642a68

Please sign in to comment.