Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions drc-portals/app/data/c2m2/DRSBundleCartButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client'
import React from 'react';
import Button from '@mui/material/Button';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; // Ensure you have @mui/icons-material installed
import { setLocalStorage } from '@/utils/localstorage';

interface DRSBundleButtonProps {
data?: { [key: string]: string | bigint | number }[];
}

function unique(L: string[]): string[] {
const S = new Set()
const U: string[] = []
for (const el of L) {
if (S.has(el)) continue
S.add(el)
U.push(el)
}
return U
}

const DRSBundleCartButton: React.FC<DRSBundleButtonProps> = ({ data }) => {
// Memoize the access_urls based on data
const access_urls = React.useMemo(() => data?.filter(({ access_url }) => !!access_url).map(({ access_url }) => access_url as string), [data]);

// Handle the copy to clipboard action
const handleDRSBundle = React.useCallback(() => {
setLocalStorage('drs-cart', cart => unique([
...(cart || '').split('\n'),
...(access_urls ?? []),
].filter(item => !!item)).join('\n'))
}, [access_urls]);

// Return early if no data is available, ensuring hooks are still called
if (!data || data.length === 0) return null;

return (
<>
<Button
variant="contained"
color="primary"
startIcon={<ShoppingCartIcon />}
onClick={handleDRSBundle}
disabled={!access_urls?.length}
>
Add to Cart
</Button>
</>
);
};

export default DRSBundleCartButton;
2 changes: 2 additions & 0 deletions drc-portals/app/data/c2m2/ExpandableTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AccordionDetails from '@mui/material/AccordionDetails';
import SearchablePagedTable, { Description } from './SearchablePagedTable';
import DownloadButton from './DownloadButton';
import DRSBundleButton from './DRSBundleButton';
import DRSBundleCartButton from './DRSBundleCartButton';
import { isURL, getNameFromBiosampleTable, getNameFromSubjectTable, getNameFromCollectionTable, getNameFromFileProjTable } from './utils';
import Link from '@/utils/link';
import { RowType } from './utils';
Expand Down Expand Up @@ -142,6 +143,7 @@ const ExpandableTable: React.FC<ExpandableTableProps> = ({
onRowSelect={handleRowSelect}
/>
<div className="flex flex-row gap-4">
{drsBundle && <DRSBundleCartButton data={dataToSend} />}
{drsBundle && <DRSBundleButton data={dataToSend} />}
<DownloadButton data={dataToSend} filename={downloadFileName} name={"Download Selected"} />
<DownloadButton data={full_data} filename={downloadFileName+"_ALL.json"} name={"Download All"} />
Expand Down
67 changes: 67 additions & 0 deletions drc-portals/app/data/c2m2/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client'
import React from 'react'
import { setLocalStorage, useLocalStorage } from '@/utils/localstorage'
import { Button, Container, Grid, Paper, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton } from '@mui/material'
import DRSBundleButton from '@/app/data/c2m2/DRSBundleButton'
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import ClearIcon from '@mui/icons-material/Clear';

export default function C2M2Cart() {
const [rawCart, setRawCart] = useLocalStorage('drs-cart')
const cart = React.useMemo(() => (rawCart||'').split('\n').filter(item => !!item), [rawCart])
return (
<Container maxWidth="xl">
<Typography variant="h2" color="secondary.dark" sx={{mt:2}} gutterBottom>DATA RESOURCE CART</Typography>
{cart.length > 0 ? <>
<Grid item>
<TableContainer component={Paper} elevation={0} variant="rounded-top" sx={{ maxHeight: 1100, width: '100%', overflowX: 'auto', maxWidth: '1100px' }}>
<Table stickyHeader aria-label="simple table" sx={{ tableLayout: 'auto', minWidth: '100%' }}>
<TableHead>
<TableRow>
<TableCell sx={{ backgroundColor: '#CAD2E9' }}>Access URL</TableCell>
<TableCell sx={{ backgroundColor: '#CAD2E9' }}>&nbsp;</TableCell>
</TableRow>
</TableHead>
<TableBody>
{cart.map((item, i) => (
<TableRow key={i}>
<TableCell>{item}</TableCell>
<TableCell>
<IconButton onClick={evt => {setRawCart(cart => (cart || '').split('\n').filter(cartItem => cartItem !== item).join('\n'))}}>
<ClearIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item>
<div className="flex flex-row gap-4">
<DRSBundleButton data={cart.map(access_url => ({ access_url }))} />
<Button
variant="contained"
color="primary"
startIcon={<CloudUploadIcon />}
disabled
>
Export to Cloud Workspace
</Button>
<Button
variant="contained"
color="error"
startIcon={<ClearIcon />}
onClick={evt => {setRawCart(() => null)}}
disabled={!cart?.length}
>
Empty Cart
</Button>
</div>
</Grid>
</> : <>
<Typography variant="body2" color="secondary.dark">Your cart is currently empty</Typography>
</>}
</Container>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Link from "@/utils/link";
import { isURL, MetadataItem, reorderStaticCols, get_partial_list_string, pruneAndRetrieveColumnNames, generateHashedJSONFilename, addCategoryColumns, getNameFromFileProjTable, Category } from "@/app/data/c2m2/utils";
import ExpandableTable from "@/app/data/c2m2/ExpandableTable";
import { Grid, Typography, Card, CardContent } from "@mui/material";
import DRSBundleCartButton from "@/app/data/c2m2/DRSBundleCartButton";
import DRSBundleButton from "@/app/data/c2m2/DRSBundleButton";
import DownloadButton from "@/app/data/c2m2/DownloadButton";

interface FileBiosTableResult {
file_bios_table_full: {
Expand Down Expand Up @@ -253,6 +256,13 @@ export default async function FilesBiosampleTableComponent({ searchParams, filte
</Typography>
) : null
))}
{'access_url' in staticFileBiosColumns && (
<div className="flex flex-row gap-4">
<DRSBundleCartButton data={fileBiosPrunedDataWithId} />
<DRSBundleButton data={fileBiosPrunedDataWithId} />
<DownloadButton data={fileBiosPrunedDataWithId} filename={downloadFilename} name={"Download Metadata"} />
</div>
)}
</CardContent>
</Card>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Link from "@/utils/link";
import { isURL, MetadataItem, reorderStaticCols, get_partial_list_string, pruneAndRetrieveColumnNames, generateHashedJSONFilename, addCategoryColumns, getNameFromFileProjTable, Category } from "@/app/data/c2m2/utils";
import ExpandableTable from "@/app/data/c2m2/ExpandableTable";
import { Grid, Typography, Card, CardContent } from "@mui/material";
import DRSBundleCartButton from "@/app/data/c2m2/DRSBundleCartButton";
import DRSBundleButton from "@/app/data/c2m2/DRSBundleButton";
import DownloadButton from "@/app/data/c2m2/DownloadButton";

interface FileProjTableResult {
file_table_full: {
Expand Down Expand Up @@ -244,6 +247,13 @@ export default async function FilesProjTableComponent({ searchParams, filterClau
</Typography>
) : null
))}
{'access_url' in staticFileProjColumns && (
<div className="flex flex-row gap-4">
<DRSBundleCartButton data={fileProjPrunedDataWithId} />
<DRSBundleButton data={fileProjPrunedDataWithId} />
<DownloadButton data={fileProjPrunedDataWithId} filename={downloadFilename} name={"Download Metadata"} />
</div>
)}
</CardContent>
</Card>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Link from "@/utils/link";
import { isURL, MetadataItem, reorderStaticCols, get_partial_list_string, pruneAndRetrieveColumnNames, generateHashedJSONFilename, addCategoryColumns, getNameFromFileProjTable, Category } from "@/app/data/c2m2/utils";
import ExpandableTable from "@/app/data/c2m2/ExpandableTable";
import { Grid, Typography, Card, CardContent } from "@mui/material";
import DRSBundleCartButton from "@/app/data/c2m2/DRSBundleCartButton";
import DRSBundleButton from "@/app/data/c2m2/DRSBundleButton";
import DownloadButton from "@/app/data/c2m2/DownloadButton";

interface FileColTableResult {
count_file_col: number;
Expand Down Expand Up @@ -248,6 +251,13 @@ export default async function FilesCollectionTableComponent({ searchParams, filt
</Typography>
) : null
))}
{'access_url' in staticFileColColumns && (
<div className="flex flex-row gap-4">
<DRSBundleCartButton data={fileColPrunedDataWithId} />
<DRSBundleButton data={fileColPrunedDataWithId} />
<DownloadButton data={fileColPrunedDataWithId} filename={downloadFilename} name={"Download Metadata"} />
</div>
)}
</CardContent>
</Card>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import Link from "@/utils/link";
import { isURL, MetadataItem, reorderStaticCols, get_partial_list_string, pruneAndRetrieveColumnNames, generateHashedJSONFilename, addCategoryColumns, getNameFromFileProjTable, Category } from "@/app/data/c2m2/utils";
import ExpandableTable from "@/app/data/c2m2/ExpandableTable";
import { Grid, Typography, Card, CardContent } from "@mui/material";
import DRSBundleCartButton from "@/app/data/c2m2/DRSBundleCartButton";
import DRSBundleButton from "@/app/data/c2m2/DRSBundleButton";
import DownloadButton from "@/app/data/c2m2/DownloadButton";

interface FileSubTableResult {

Expand Down Expand Up @@ -255,6 +258,13 @@ export default async function FilesSubjectTableComponent({ searchParams, filterC
</Typography>
) : null
))}
{'access_url' in staticFileSubColumns && (
<div className="flex flex-row gap-4">
<DRSBundleCartButton data={fileSubPrunedDataWithId} />
<DRSBundleButton data={fileSubPrunedDataWithId} />
<DownloadButton data={fileSubPrunedDataWithId} filename={downloadFilename} name={"Download Metadata"} />
</div>
)}
</CardContent>
</Card>
</Grid>
Expand Down
2 changes: 2 additions & 0 deletions drc-portals/app/data/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Header from '@/components/Header/'
import Footer from '@/components/Footer/data'
import { Metadata } from 'next'
import Background from '@/components/styled/background'
import CartFab from '@/components/Cart/data'
// import NavBreadcrumbs from '@/components/Header/breadcrumbs'

export const metadata: Metadata = {
Expand Down Expand Up @@ -78,6 +79,7 @@ export default function DataLayout({
<div>{children}</div>
</Background>
</Grid>
<CartFab />
<Grid item><Footer/></Grid>
</>
)
Expand Down
29 changes: 29 additions & 0 deletions drc-portals/components/Cart/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'
import Grid from '@mui/material/Grid'
import Link from '@/utils/link'
import { Badge, Fab } from '@mui/material'
import Icon from "@mdi/react"
import { mdiCart } from '@mdi/js'
import React from 'react'
import { useLocalStorage } from '@/utils/localstorage'

export default function CartFab() {
const [rawCart, setRawCart] = useLocalStorage('drs-cart')
const cart = React.useMemo(() => (rawCart||'').split('\n').filter(item => !!item), [rawCart])
return (
cart.length > 0 && <Grid item sx={{
zIndex: 1050,
position: 'fixed',
bottom: 50,
right: 50,
}}>
<Link href="/data/c2m2/cart">
<Badge badgeContent={cart.length} color="error" overlap='circular'>
<Fab sx={{ zIndex: 0 }} aria-label={'cart'} color={'primary'}>
<Icon style={{ color: "#336699" }} path={mdiCart} size={1} />
</Fab>
</Badge>
</Link>
</Grid>
)
}
38 changes: 38 additions & 0 deletions drc-portals/utils/localstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'

/**
* Set local storage and ensure the rest of the application gets the StorageEvent
*/
export function setLocalStorage(key: string, update: (oldValue: string | null) => string | null) {
const oldValue = window.localStorage.getItem(key)
const newValue = update(oldValue)
if (newValue === null) {
window.localStorage.removeItem(key)
} else {
window.localStorage.setItem(key, newValue)
}
window.dispatchEvent(new StorageEvent('storage', { key, oldValue, newValue }))
}

/**
* Use localstorage as you would normal react state
*
* const [myState, setMyState] = useLocalStorage('localStorageKey')
*
* Only difference, it must be string|null
* & setMyState only takes a value not a callback
*/
export function useLocalStorage(key: string) {
const [value, _setValue] = React.useState(() => window.localStorage.getItem(key))
const storageListener = React.useCallback((evt: StorageEvent) => {
if (evt.key === key) {
_setValue(() => evt.newValue)
}
}, [key])
React.useEffect(() => {
window.addEventListener('storage', storageListener)
return () => {window.removeEventListener('storage', storageListener)}
}, [key])
const setValue = React.useCallback((update: (oldValue: string | null) => string | null) => setLocalStorage(key, update), [key])
return [value, setValue] as const
}