@mui/material#Badge TypeScript Examples
The following examples show how to use
@mui/material#Badge.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: SetupScreen.tsx From rewind with MIT License | 6 votes |
function DirectorySelection({ value, onChange, placeHolder, badgeOnEmpty }: DirectorySelectionProps) {
const handleSelectFolderClick = useCallback(() => {
window.api.selectDirectory(value ?? "").then((path) => {
if (path !== null) {
onChange(path);
}
});
}, [onChange, value]);
const onInputChange = useCallback(
(event) => {
onChange(event.target.value);
},
[onChange],
);
const invisibleBadge = !badgeOnEmpty || !!value;
return (
<Paper sx={{ px: 2, py: 1, display: "flex", alignItems: "center", width: 400 }} elevation={2}>
{/*<span className={"text-gray-400 select-none w-96"}>{value ?? placeHolder}</span>*/}
<InputBase sx={{ flex: 1 }} placeholder={placeHolder} value={value} onChange={onInputChange} disabled={true} />
<IconButton onClick={handleSelectFolderClick}>
<Badge invisible={invisibleBadge} color={"primary"} variant={"dot"}>
<FolderIcon />
</Badge>
</IconButton>
</Paper>
);
}
Example #2
Source File: Profiler.tsx From NekoMaid with MIT License | 6 votes |
ProfilerIcon: React.FC = () => {
const plugin = usePlugin()
const globalData = useGlobalData()
const [status, setStatus] = useState(!!globalData.profilerStarted)
useEffect(() => {
const off = plugin.on('profiler:status', (res: boolean) => {
setStatus(globalData.profilerStarted = res)
})
return () => { off() }
})
return <Badge color='secondary' variant='dot' invisible={!status}><Equalizer /></Badge>
}
Example #3
Source File: MarketItem.tsx From Cromwell with MIT License | 5 votes |
export default function MarketItem(props: PropsType) {
const data = props?.data;
const [installing, setInstalling] = useState(false);
const [installed, setInstalled] = useState(!!(props.data?.name
&& props?.listItemProps?.installedModules?.find(inst => inst.name === props.data?.name)));
const installModule = async () => {
if (!props.listItemProps?.install || !data) return;
setInstalling(true);
const success = await props.listItemProps.install(data);
if (success) setInstalled(true);
setInstalling(false);
}
return (
<Grid item xs={6} lg={4} className={styles.listItem}>
<div className={clsx(styles.listItemContent, installing && styles.installing)}>
{data?.image && (
<CardActionArea
onClick={() => props.listItemProps?.open(props.data)}
className={styles.cardActionArea}
>
<img src={data.image} className={styles.image} />
</CardActionArea>
)}
<div className={styles.caption}>
<Badge color="secondary" badgeContent={installed ? 'installed' : null}>
<Typography gutterBottom variant="h5" component="h3" className={styles.title}>
{data?.title ?? ''}
</Typography>
</Badge>
<p className={styles.version}>{data?.version ?? ''}</p>
<p className={styles.excerpt}>{data?.excerpt ?? ''}</p>
</div>
<div className={styles.actions}>
<Button
size="small" color="primary" variant="contained"
onClick={() => props.listItemProps?.open(props.data)}
>Open</Button>
<Button
disabled={installed || installing}
size="small" color="primary" variant="contained"
onClick={installModule}
>Install</Button>
</div>
{installing && (
<LinearProgress className={styles.updateProgress} />
)}
</div>
</Grid>
)
}
Example #4
Source File: MarketModal.tsx From Cromwell with MIT License | 5 votes |
export default function MarketModal(props: {
data: TCCSModuleInfo | TPackageCromwellConfig;
installedModules?: TPackageCromwellConfig[];
noInstall?: boolean;
install?: (info: TCCSModuleInfo | TPackageCromwellConfig) => Promise<boolean>;
}) {
const { data, installedModules } = props;
const [installing, setInstalling] = useState(false);
const [installed, setInstalled] = useState(!!(data?.name &&
installedModules?.find(inst => inst.name === data?.name)));
const installModule = async () => {
if (!props.install) return;
setInstalling(true);
const success = await props.install(data);
if (success) setInstalled(true);
setInstalling(false);
}
return (
<div className={styles.MarketModal}>
<div className={styles.header}>
<div>
<Badge color="secondary" badgeContent={installed ? 'installed' : null}>
<Typography gutterBottom variant="h5" component="h3" className={styles.title}>
{data?.title ?? ''}
</Typography>
</Badge>
<p className={styles.version}>{data?.version ?? ''} by {data?.author ?? ''}</p>
</div>
<div>
{data?.link && (
<Tooltip title="Documentation">
<IconButton
onClick={() => window.open(data.link, '_blank')}>
<HelpOutlineIcon />
</IconButton>
</Tooltip>
)}
{!props.noInstall && (
<Button
disabled={installed || installing}
size="small" color="primary" variant="contained"
onClick={installModule}
>Install</Button>
)}
</div>
</div>
{data?.images && (
<div className={styles.images}>
<CGallery
id="MarketModal_gallery"
gallery={{
images: data.images.map(url => ({
src: url
})),
height: 300,
navigation: true,
thumbs: {
backgroundSize: 'contain'
},
backgroundSize: 'contain',
lazy: true,
visibleSlides: 1,
}}
/>
</div>
)}
{data?.description && (
<div className={styles.description}
dangerouslySetInnerHTML={{ __html: data?.description }}></div>
)}
</div>
)
}
Example #5
Source File: AccountList.tsx From abrechnung with GNU Affero General Public License v3.0 | 5 votes |
TextBadge = styled(Badge)<BadgeProps>(({ theme }: { theme: Theme }) => ({
"& .MuiBadge-badge": {
right: -12,
border: `2px solid ${theme.palette.background.paper}`,
padding: "0 4px",
marginRight: "5px",
},
}))
Example #6
Source File: LeftMenuSidebar.tsx From rewind with MIT License | 5 votes |
export function LeftMenuSidebar() {
// const LinkBehavior = React.forwardRef((props, ref) => <Link ref={ref} to="/" {...props} role={undefined} />);
const dispatch = useAppDispatch();
const pathname = useAppSelector((state) => state.router.location.pathname);
const handleLinkClick = (to: string) => () => dispatch(push(to));
const buttonColor = (name: string) => (name === pathname ? "primary" : "default");
const updateState = useCheckForUpdate();
return (
<Stack
sx={{
width: (theme) => theme.spacing(10),
paddingBottom: 2,
}}
gap={1}
p={1}
alignItems={"center"}
component={"nav"}
>
<Box onClick={handleLinkClick("/home")} sx={{ cursor: "pointer" }}>
<RewindLogo />
</Box>
<Divider orientation={"horizontal"} sx={{ borderWidth: 1, width: "80%" }} />
<Tooltip title={"Overview"} placement={"right"}>
<IconButton color={buttonColor("/home")} onClick={handleLinkClick("/home")}>
<Home />
</IconButton>
</Tooltip>
<Tooltip title={"Analyzer"} placement={"right"}>
<IconButton
// These are not centered
onClick={handleLinkClick("/analyzer")}
color={buttonColor("/analyzer")}
>
<FaMicroscope height={"0.75em"} />
</IconButton>
</Tooltip>
{/*Nothing*/}
<Box flexGrow={1} />
{updateState.hasNewUpdate && (
<Tooltip title={`New version ${updateState.latestVersion} available!`} placement={"right"}>
<IconButton onClick={() => window.open(latestReleaseUrl)}>
<Badge variant={"dot"} color={"error"}>
<UpdateIcon />
</Badge>
</IconButton>
</Tooltip>
)}
</Stack>
);
}
Example #7
Source File: TableHeader.tsx From firecms with MIT License | 4 votes |
function TableHeaderInternal<M extends { [Key: string]: any }>({
sort,
onColumnSort,
onFilterUpdate,
filter,
column
}: TableHeaderProps<M>) {
const [onHover, setOnHover] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const classes = useStyles({ onHover, align: column.align });
const [open, setOpen] = React.useState(false);
const handleSettingsClick = useCallback((event: any) => {
setOpen(true);
}, []);
const handleClose = useCallback(() => {
setOpen(false);
}, []);
const update = useCallback((filterForProperty?: [TableWhereFilterOp, any]) => {
onFilterUpdate(filterForProperty);
setOpen(false);
}, []);
return (
<ErrorBoundary>
<Grid
className={classes.header}
ref={ref}
wrap={"nowrap"}
alignItems={"center"}
onMouseEnter={() => setOnHover(true)}
onMouseMove={() => setOnHover(true)}
onMouseLeave={() => setOnHover(false)}
container>
<Grid item xs={true} className={classes.headerTitle}>
<div className={classes.headerInternal}>
<div className={classes.headerIcon}>
{column.icon && column.icon(onHover || open)}
</div>
<div className={classes.headerTitleInternal}>
{column.label}
</div>
</div>
</Grid>
{column.sortable && (sort || onHover || open) &&
<Grid item>
<Badge color="secondary"
variant="dot"
overlap="circular"
invisible={!sort}>
<IconButton
size={"small"}
className={classes.headerIconButton}
onClick={() => {
onColumnSort(column.key as Extract<keyof M, string>);
}}
>
{!sort && <ArrowUpwardIcon fontSize={"small"}/>}
{sort === "asc" &&
<ArrowUpwardIcon fontSize={"small"}/>}
{sort === "desc" &&
<ArrowDownwardIcon fontSize={"small"}/>}
</IconButton>
</Badge>
</Grid>
}
{column.filter && <Grid item>
<Badge color="secondary"
variant="dot"
overlap="circular"
invisible={!filter}>
<IconButton
className={classes.headerIconButton}
size={"small"}
onClick={handleSettingsClick}>
<ArrowDropDownCircleIcon fontSize={"small"}
color={onHover || open ? undefined : "disabled"}/>
</IconButton>
</Badge>
</Grid>}
</Grid>
{column.sortable && <Popover
id={open ? `popover_${column.key}` : undefined}
open={open}
elevation={2}
anchorEl={ref.current}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "right"
}}
>
<FilterForm column={column}
filter={filter}
onFilterUpdate={update}/>
</Popover>}
</ErrorBoundary>
);
}
Example #8
Source File: StoreCurrencies.tsx From Cromwell with MIT License | 4 votes |
export default function StoreCurrencies(props: TTabProps) {
const { settings, changeSettings } = props;
const currencies = settings?.currencies ?? [];
const handleDeleteCurrency = (tag: string) => {
const currencies = (settings?.currencies ?? []);
changeSettings('currencies', currencies.filter(curr => curr.tag !== tag));
}
const handleAddCurrency = () => {
const currencies = (settings?.currencies ?? []);
changeSettings('currencies', [...currencies, {
tag: 'new',
id: currencies.length + 1 + '',
}]);
}
return (
<Grid container spacing={3}>
<Grid item xs={12}>
<ResponsiveGridLayout
margin={[15, 15]}
layouts={{
xs: currencies.map((currency, index) => {
return { i: currency.id, x: (index % 2), y: Math.floor(index / 2), w: 1, h: 1 }
}),
xxs: currencies.map((currency, index) => {
return { i: currency.id, x: 0, y: index, w: 1, h: 1 }
})
}}
breakpoints={{ xs: 480, xxs: 0 }}
rowHeight={330}
isResizable={false}
cols={{ xs: 2, xxs: 1 }}
draggableHandle='.draggableHandle'
onLayoutChange={(layout) => {
const sortedCurrencies: TCurrency[] = [];
const sorted = [...layout].sort((a, b) => (a.x + a.y * 10) - (b.x + b.y * 10));
sorted.forEach(item => {
const curr = currencies.find(curr => curr.id === item.i);
if (curr) sortedCurrencies.push(curr);
});
changeSettings('currencies', sortedCurrencies)
}}
>
{currencies.map((currency, index) => {
const onChange = (item) => {
const newCurrencies = [...currencies];
newCurrencies[index] = item;
changeSettings('currencies', newCurrencies)
}
const handleTextFieldChange = (key: keyof TCurrency, type?: 'number' | 'string') =>
(event: React.ChangeEvent<{ value: string }>) => {
let val: string | number | undefined = event.target.value;
if (type === 'number') {
val = parseFloat(val);
if (isNaN(val)) val = undefined;
}
onChange({
...currency,
[key]: val
})
}
const isPrimary = index === 0;
return (
<div className={styles.currencyItem} key={currency.id}>
<div className={styles.currencyItemHeader}>
<div className={styles.currencyBadge}>
<Badge color="secondary" badgeContent={isPrimary ? 'Primary' : null}>
<div></div>
</Badge>
</div>
<div>
<IconButton onClick={() => handleDeleteCurrency(currency.tag)}>
<DeleteForeverIcon />
</IconButton>
<IconButton style={{ cursor: 'move' }} className="draggableHandle">
<DragIndicatorIcon />
</IconButton>
</div>
</div>
<TextField label="Tag"
value={currency.tag ?? ''}
className={styles.textField + ' draggableCancel'}
fullWidth
variant="standard"
onChange={handleTextFieldChange('tag')}
/>
<TextField label="Title"
value={currency.title ?? ''}
className={styles.textField + ' draggableCancel'}
fullWidth
variant="standard"
onChange={handleTextFieldChange('title')}
/>
<TextField label="Ratio"
value={currency.ratio ?? ''}
className={styles.textField + ' draggableCancel'}
fullWidth
type="number"
variant="standard"
onChange={handleTextFieldChange('ratio', 'number')}
/>
<TextField label="Symbol"
value={currency.symbol ?? ''}
className={styles.textField + ' draggableCancel'}
fullWidth
variant="standard"
onChange={handleTextFieldChange('symbol')}
/>
</div>
)
})}
</ResponsiveGridLayout>
<Tooltip title="Add currency">
<IconButton onClick={handleAddCurrency}>
<AddIcon />
</IconButton>
</Tooltip>
</Grid>
</Grid>
)
}
Example #9
Source File: ThemeList.tsx From Cromwell with MIT License | 4 votes |
render() {
const { isLoading, packages, installedThemes, cmsConfig, isChangingTheme } = this.state;
return (
<div className={styles.ThemeList} ref={this.pageRef}>
<Button
className={styles.addBtn}
onClick={this.handleOpenMarket}
variant="contained"
color="primary"
startIcon={<AddCircleOutlineIcon />}
>Add themes</Button>
{isLoading && (
<div className={styles.list}>
{Array(2).fill(1).map((it, index) => (
<Skeleton key={index} variant="rectangular" height="388px" width="300px" style={{ margin: '0 10px 20px 10px' }} > </Skeleton>
))}
</div>
)}
{!isLoading &&
<div className={styles.list}>
{packages.map(info => {
const isActive = Boolean(cmsConfig && cmsConfig.themeName === info.name);
const entity = installedThemes?.find(ent => ent.name === info.name);
const isInstalled = entity?.isInstalled ?? false;
const availableUpdate = this.themeUpdates[info.name];
const isUnderUpdate = this.themeUnderUpdate[info.name];
return (
<div className={`${styles.themeCard} ${commonStyles.paper}`} key={info.name}>
<CardActionArea
className={styles.cardActionArea}
style={{ opacity: isUnderUpdate ? 0.5 : 1 }}
>
<div
style={{ backgroundImage: `url("data:image/png;base64,${info.image}")` }}
className={styles.themeImage}
></div>
<CardContent className={styles.mainInfo}>
<Badge color="secondary" badgeContent={isActive ? 'Active' : null}>
<Typography gutterBottom variant="h5" component="h2" className={styles.themeTitle}>
{info.title}
</Typography>
</Badge>
<p className={styles.version}
onClick={this.handleShowUpdate(entity, info, availableUpdate)}
style={{ cursor: availableUpdate ? 'pointer' : 'initial' }}
>{(info?.version ?? '') +
(availableUpdate ? ' > ' + availableUpdate.version + ' Open info' : '')}</p>
<Typography variant="body2" color="textSecondary" component="p">
{info.excerpt}
</Typography>
</CardContent>
</CardActionArea>
<CardActions
style={{ opacity: isUnderUpdate ? 0.5 : 1 }}
className={styles.themeActions}
disableSpacing
>
{!isInstalled && (
<Button
disabled={isUnderUpdate || isChangingTheme}
size="small" color="primary" variant="contained"
onClick={this.handleActivateTheme(info.name)}
>Install theme</Button>
)}
{isInstalled && isActive && (
<Button
disabled={isUnderUpdate || isChangingTheme}
size="small" color="primary" variant="contained"
onClick={() => {
const route = `${themeEditPageInfo.baseRoute}`;
this.props.history.push(route);
}}
>
Edit theme
</Button>
)}
{availableUpdate && (
<Button
disabled={isUnderUpdate || isChangingTheme}
size="small" color="primary" variant="contained"
onClick={() => this.startUpdate(info)}
>Update</Button>
)}
{isInstalled && !isActive && (
<Button size="small" color="primary" variant="contained"
onClick={() => this.handleSetActiveTheme(info)}
disabled={isUnderUpdate || isChangingTheme}
>Set active</Button>
)}
<Button size="small" color="primary" variant="outlined"
disabled={isUnderUpdate || isChangingTheme}
onClick={() => this.handleDelete(info)}
>Delete</Button>
<Button size="small" color="primary" variant="outlined"
onClick={() => this.openTheme(info)}
>Info</Button>
{isUnderUpdate && (
<LinearProgress className={styles.updateProgress} />
)}
</CardActions>
</div>
)
})}
</div>}
<LoadingStatus isActive={isChangingTheme} />
{/* <ManagerLogger isActive={isChangingTheme} /> */}
<Modal
open={!!this.state.updateModalInfo}
onClose={() => this.setState({ updateModalInfo: null })}
className={commonStyles.center}
blurSelector="#root"
>
<UpdateModalContent
underUpdate={this.themeUnderUpdate}
{...(this.state?.updateModalInfo ?? {})}
onStartUpdate={this.startUpdate}
onClose={() => this.setState({ updateModalInfo: null })}
/>
</Modal>
<Modal
open={!!this.state.openedTheme}
blurSelector="#root"
className={commonStyles.center}
onClose={() => this.setState({ openedTheme: undefined })}
>
{this.state?.openedTheme && (
<MarketModal
installedModules={this.state?.installedThemes ?? []}
data={this.state.openedTheme}
noInstall
/>
)}
</Modal>
</div>
)
}
Example #10
Source File: TabOverview.tsx From genshin-optimizer with MIT License | 4 votes |
export default function TabOverview() {
const { data, characterSheet, character, character: { key: characterKey } } = useContext(DataContext)
const characterDispatch = useCharacterReducer(characterKey)
const navigate = useNavigate()
const { t } = useTranslation("page_character")
const charEle = data.get(input.charEle).value as ElementKey
const weaponTypeKey = characterSheet.weaponTypeKey
const level = data.get(input.lvl).value
const ascension = data.get(input.asc).value
const constellation = data.get(input.constellation).value
const tlvl = {
auto: data.get(input.total.auto).value,
skill: data.get(input.total.skill).value,
burst: data.get(input.total.burst).value,
}
const tBoost = {
auto: data.get(input.bonus.auto).value,
skill: data.get(input.bonus.skill).value,
burst: data.get(input.bonus.burst).value,
}
return <Grid container spacing={1} sx={{ justifyContent: "center" }}>
<Grid item xs={8} sm={5} md={4} lg={2.5} >
{/* Image card with star and name and level */}
<CardLight >
<Box src={characterSheet.cardImg} component="img" width="100%" height="auto" />
<CardContent>
<Typography variant="h5" >
{characterSheet.name}
<ImgIcon sx={{ pr: 0.5 }} src={Assets.weaponTypes?.[weaponTypeKey]} />
{StatIcon[charEle]}
<IconButton sx={{ p: 0.5, mt: -0.5 }} onClick={() => characterDispatch({ favorite: !character.favorite })}>
{character.favorite ? <Favorite /> : <FavoriteBorder />}
</IconButton>
</Typography>
<Typography variant="h6"><Stars stars={characterSheet.rarity} colored /></Typography>
<Typography variant="h5">Lvl. {CharacterSheet.getLevelString(level, ascension)}</Typography>
<CardActionArea sx={{ p: 1 }} onClick={() => navigate("talent")}>
<Grid container spacing={1} mt={-1}>
{(["auto", "skill", "burst"] as TalentSheetElementKey[]).map(tKey =>
<Grid item xs={4} key={tKey}>
<Badge badgeContent={tlvl[tKey]} color={tBoost[tKey] ? "info" : "secondary"}
overlap="circular"
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
sx={{
width: "100%",
height: "100%",
"& > .MuiBadge-badge": {
fontSize: "1.25em",
padding: ".25em .4em",
borderRadius: ".5em",
lineHeight: 1,
height: "1.25em"
}
}}>
<Box component="img" src={characterSheet.getTalentOfKey(tKey, charEle)?.img} width="100%" height="auto" />
</Badge>
</Grid>)}
</Grid>
</CardActionArea>
<Typography sx={{ textAlign: "center", mt: 1 }} variant="h6">{characterSheet.constellationName}</Typography>
<Grid container spacing={1}>
{range(1, 6).map(i =>
<Grid item xs={4} key={i}>
<CardActionArea onClick={() => characterDispatch({ constellation: i === constellation ? i - 1 : i })}>
<Box component="img" src={characterSheet.getTalentOfKey(`constellation${i}` as TalentSheetElementKey, charEle)?.img}
sx={{
...(constellation >= i ? {} : { filter: "brightness(50%)" })
}}
width="100%" height="auto" />
</CardActionArea>
</Grid>)}
</Grid>
<Typography sx={{ textAlign: "center", mt: 1 }} variant="h6">{t("teammates")}</Typography>
<CardActionArea sx={{ p: 1 }} onClick={() => navigate("teambuffs")}>
<Grid container columns={3} spacing={1}>
{range(0, 2).map(i => <Grid key={i} item xs={1} height="100%"><CharacterCardPico characterKey={character.team[i]} index={i} /></Grid>)}
</Grid>
</CardActionArea>
</CardContent>
</CardLight>
</Grid>
<Grid item xs={12} sm={7} md={8} lg={9.5} sx={{
display: "flex", flexDirection: "column", gap: 1
}} >
<Grid container spacing={1} columns={{ xs: 2, sm: 2, md: 3, lg: 4, xl: 6 }}>
<Grid item xs={1}>
<WeaponCardNano weaponId={character.equippedWeapon} BGComponent={CardLight} onClick={() => navigate("equip")} />
</Grid>
{allSlotKeys.map(slotKey =>
<Grid item key={slotKey} xs={1} >
<ArtifactCardNano artifactId={data.get(input.art[slotKey].id).value} slotKey={slotKey} BGComponent={CardLight} onClick={() => navigate("equip")} />
</Grid>)}
</Grid>
<MainStatsCards />
</Grid>
</Grid >
}
Example #11
Source File: ListObjects.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
ListObjects = ({ match, history }: IListObjectsProps) => {
const classes = useStyles();
const dispatch = useDispatch();
const rewindEnabled = useSelector(
(state: AppState) => state.objectBrowser.rewind.rewindEnabled
);
const rewindDate = useSelector(
(state: AppState) => state.objectBrowser.rewind.dateToRewind
);
const bucketToRewind = useSelector(
(state: AppState) => state.objectBrowser.rewind.bucketToRewind
);
const versionsMode = useSelector(
(state: AppState) => state.objectBrowser.versionsMode
);
const searchObjects = useSelector(
(state: AppState) => state.objectBrowser.searchObjects
);
const showDeleted = useSelector(
(state: AppState) => state.objectBrowser.showDeleted
);
const detailsOpen = useSelector(
(state: AppState) => state.objectBrowser.objectDetailsOpen
);
const selectedInternalPaths = useSelector(
(state: AppState) => state.objectBrowser.selectedInternalPaths
);
const loading = useSelector(
(state: AppState) => state.objectBrowser.loadingObjects
);
const simplePath = useSelector(
(state: AppState) => state.objectBrowser.simplePath
);
const loadingBucket = useSelector(selBucketDetailsLoading);
const bucketInfo = useSelector(selBucketDetailsInfo);
const allowResources = useSelector(
(state: AppState) => state.console.session.allowResources
);
const [records, setRecords] = useState<BucketObjectItem[]>([]);
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
const [loadingStartTime, setLoadingStartTime] = useState<number>(0);
const [loadingMessage, setLoadingMessage] =
useState<React.ReactNode>(defLoading);
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
const [isVersioned, setIsVersioned] = useState<boolean>(false);
const [loadingLocking, setLoadingLocking] = useState<boolean>(true);
const [lockingEnabled, setLockingEnabled] = useState<boolean>(false);
const [rewindSelect, setRewindSelect] = useState<boolean>(false);
const [selectedObjects, setSelectedObjects] = useState<string[]>([]);
const [previewOpen, setPreviewOpen] = useState<boolean>(false);
const [selectedPreview, setSelectedPreview] =
useState<BucketObjectItem | null>(null);
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
const [sortDirection, setSortDirection] = useState<
"ASC" | "DESC" | undefined
>("ASC");
const [currentSortField, setCurrentSortField] = useState<string>("name");
const [iniLoad, setIniLoad] = useState<boolean>(false);
const [canShareFile, setCanShareFile] = useState<boolean>(false);
const [canPreviewFile, setCanPreviewFile] = useState<boolean>(false);
const [quota, setQuota] = useState<BucketQuota | null>(null);
const internalPaths = get(match.params, "subpaths", "");
const bucketName = match.params["bucketName"];
const fileUpload = useRef<HTMLInputElement>(null);
const folderUpload = useRef<HTMLInputElement>(null);
useEffect(() => {
if (folderUpload.current !== null) {
folderUpload.current.setAttribute("directory", "");
folderUpload.current.setAttribute("webkitdirectory", "");
}
}, [folderUpload]);
useEffect(() => {
if (selectedObjects.length === 1) {
const objectName = selectedObjects[0];
if (extensionPreview(objectName) !== "none") {
setCanPreviewFile(true);
} else {
setCanPreviewFile(false);
}
if (objectName.endsWith("/")) {
setCanShareFile(false);
} else {
setCanShareFile(true);
}
} else {
setCanShareFile(false);
setCanPreviewFile(false);
}
}, [selectedObjects]);
useEffect(() => {
if (!quota) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/quota`)
.then((res: BucketQuota) => {
let quotaVals = null;
if (res.quota) {
quotaVals = res;
}
setQuota(quotaVals);
})
.catch((err) => {
console.error("Error Getting Quota Status: ", err.detailedError);
setQuota(null);
});
}
}, [quota, bucketName]);
useEffect(() => {
if (selectedObjects.length > 0) {
dispatch(setObjectDetailsView(true));
return;
}
if (selectedObjects.length === 0 && selectedInternalPaths === null) {
dispatch(setObjectDetailsView(false));
}
}, [selectedObjects, selectedInternalPaths, dispatch]);
const displayDeleteObject = hasPermission(bucketName, [
IAM_SCOPES.S3_DELETE_OBJECT,
]);
const displayListObjects = hasPermission(bucketName, [
IAM_SCOPES.S3_LIST_BUCKET,
]);
const updateMessage = () => {
let timeDelta = Date.now() - loadingStartTime;
if (timeDelta / 1000 >= 6) {
setLoadingMessage(
<Fragment>
<Typography component="h3">
This operation is taking longer than expected... (
{Math.ceil(timeDelta / 1000)}s)
</Typography>
</Fragment>
);
} else if (timeDelta / 1000 >= 3) {
setLoadingMessage(
<Typography component="h3">
This operation is taking longer than expected...
</Typography>
);
}
};
useEffect(() => {
if (!iniLoad) {
dispatch(setBucketDetailsLoad(true));
setIniLoad(true);
}
}, [iniLoad, dispatch, setIniLoad]);
useInterval(() => {
// Your custom logic here
if (loading) {
updateMessage();
}
}, 1000);
useEffect(() => {
if (loadingVersioning) {
if (displayListObjects) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
.then((res: BucketVersioning) => {
setIsVersioned(res.is_versioned);
setLoadingVersioning(false);
})
.catch((err: ErrorResponseHandler) => {
console.error(
"Error Getting Object Versioning Status: ",
err.detailedError
);
setLoadingVersioning(false);
});
} else {
setLoadingVersioning(false);
setRecords([]);
}
}
}, [bucketName, loadingVersioning, dispatch, displayListObjects]);
useEffect(() => {
if (loadingLocking) {
if (displayListObjects) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
.then((res: BucketObjectLocking) => {
setLockingEnabled(res.object_locking_enabled);
setLoadingLocking(false);
})
.catch((err: ErrorResponseHandler) => {
console.error(
"Error Getting Object Locking Status: ",
err.detailedError
);
setLoadingLocking(false);
});
} else {
setRecords([]);
setLoadingLocking(false);
}
}
}, [bucketName, loadingLocking, dispatch, displayListObjects]);
useEffect(() => {
const decodedIPaths = decodeURLString(internalPaths);
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
dispatch(setObjectDetailsView(false));
dispatch(setSelectedObjectView(null));
dispatch(
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
);
} else {
dispatch(setLoadingObjectInfo(true));
dispatch(setObjectDetailsView(true));
dispatch(setLoadingVersions(true));
dispatch(
setSelectedObjectView(
`${decodedIPaths ? `${encodeURLString(decodedIPaths)}` : ``}`
)
);
dispatch(
setSimplePathHandler(
`${decodedIPaths.split("/").slice(0, -1).join("/")}/`
)
);
}
}, [internalPaths, rewindDate, rewindEnabled, dispatch]);
useEffect(() => {
dispatch(setSearchObjects(""));
dispatch(setLoadingObjectsList(true));
setSelectedObjects([]);
}, [simplePath, dispatch, setSelectedObjects]);
useEffect(() => {
if (loading) {
if (displayListObjects) {
let pathPrefix = "";
if (internalPaths) {
const decodedPath = decodeURLString(internalPaths);
pathPrefix = decodedPath.endsWith("/")
? decodedPath
: decodedPath + "/";
}
let currentTimestamp = Date.now();
setLoadingStartTime(currentTimestamp);
setLoadingMessage(defLoading);
// We get URL to look into
let urlTake = `/api/v1/buckets/${bucketName}/objects`;
// Is rewind enabled?, we use Rewind API
if (rewindEnabled) {
if (bucketToRewind !== bucketName) {
dispatch(resetRewind());
return;
}
if (rewindDate) {
const rewindParsed = rewindDate.toISOString();
urlTake = `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}`;
}
} else if (showDeleted) {
// Do we want to display deleted items too?, we use rewind to current time to show everything
const currDate = new Date();
const currDateISO = currDate.toISOString();
urlTake = `/api/v1/buckets/${bucketName}/rewind/${currDateISO}`;
}
api
.invoke(
"GET",
`${urlTake}${
pathPrefix ? `?prefix=${encodeURLString(pathPrefix)}` : ``
}`
)
.then((res: BucketObjectItemsList) => {
const records: BucketObjectItem[] = res.objects || [];
const folders: BucketObjectItem[] = [];
const files: BucketObjectItem[] = [];
// We separate items between folders or files to display folders at the beginning always.
records.forEach((record) => {
// We omit files from the same path
if (record.name !== decodeURLString(internalPaths)) {
// this is a folder
if (record.name.endsWith("/")) {
folders.push(record);
} else {
// this is a file
files.push(record);
}
}
});
const recordsInElement = [...folders, ...files];
if (recordsInElement.length === 0 && pathPrefix !== "") {
let pathTest = `/api/v1/buckets/${bucketName}/objects${
internalPaths ? `?prefix=${internalPaths}` : ""
}`;
if (rewindEnabled) {
const rewindParsed = rewindDate.toISOString();
let pathPrefix = "";
if (internalPaths) {
const decodedPath = decodeURLString(internalPaths);
pathPrefix = decodedPath.endsWith("/")
? decodedPath
: decodedPath + "/";
}
pathTest = `/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${
pathPrefix ? `?prefix=${encodeURLString(pathPrefix)}` : ``
}`;
}
api
.invoke("GET", pathTest)
.then((res: BucketObjectItemsList) => {
//It is a file since it has elements in the object, setting file flag and waiting for component mount
if (!res.objects) {
// It is a folder, we remove loader & set original results list
dispatch(setLoadingObjectsList(false));
setRecords(recordsInElement);
} else {
// This code prevents the program from opening a file when a substring of that file is entered as a new folder.
// Previously, if there was a file test1.txt and the folder test was created with the same prefix, the program
// would open test1.txt instead
let found = false;
let pathPrefixChopped = pathPrefix.slice(
0,
pathPrefix.length - 1
);
for (let i = 0; i < res.objects.length; i++) {
if (res.objects[i].name === pathPrefixChopped) {
found = true;
}
}
if (
(res.objects.length === 1 &&
res.objects[0].name.endsWith("/")) ||
!found
) {
// This is a folder, we set the original results list
setRecords(recordsInElement);
} else {
// This is a file. We change URL & Open file details view.
dispatch(setObjectDetailsView(true));
dispatch(setSelectedObjectView(internalPaths));
// We split the selected object URL & remove the last item to fetch the files list for the parent folder
const parentPath = `${decodeURLString(internalPaths)
.split("/")
.slice(0, -1)
.join("/")}/`;
api
.invoke(
"GET",
`${urlTake}${
pathPrefix
? `?prefix=${encodeURLString(parentPath)}`
: ``
}`
)
.then((res: BucketObjectItemsList) => {
const records: BucketObjectItem[] = res.objects || [];
setRecords(records);
})
.catch(() => {});
}
dispatch(setLoadingObjectsList(false));
}
})
.catch((err: ErrorResponseHandler) => {
dispatch(setLoadingObjectsList(false));
dispatch(setErrorSnackMessage(err));
});
} else {
setRecords(recordsInElement);
dispatch(setLoadingObjectsList(false));
}
})
.catch((err: ErrorResponseHandler) => {
const permitItems = permissionItems(
bucketName,
pathPrefix,
allowResources || []
);
if (!permitItems || permitItems.length === 0) {
dispatch(setErrorSnackMessage(err));
} else {
setRecords(permitItems);
}
dispatch(setLoadingObjectsList(false));
});
} else {
dispatch(setLoadingObjectsList(false));
}
}
}, [
loading,
match,
dispatch,
bucketName,
rewindEnabled,
rewindDate,
internalPaths,
bucketInfo,
showDeleted,
displayListObjects,
bucketToRewind,
allowResources,
]);
// bucket info
useEffect(() => {
if (loadingBucket) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}`)
.then((res: BucketInfo) => {
dispatch(setBucketDetailsLoad(false));
dispatch(setBucketInfo(res));
})
.catch((err: ErrorResponseHandler) => {
dispatch(setBucketDetailsLoad(false));
dispatch(setErrorSnackMessage(err));
});
}
}, [bucketName, loadingBucket, dispatch]);
const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
setDeleteMultipleOpen(false);
if (refresh) {
dispatch(setSnackBarMessage(`Objects deleted successfully.`));
setSelectedObjects([]);
dispatch(setLoadingObjectsList(true));
}
};
const handleUploadButton = (e: any) => {
if (
e === null ||
e === undefined ||
e.target.files === null ||
e.target.files === undefined
) {
return;
}
e.preventDefault();
var newFiles: File[] = [];
for (var i = 0; i < e.target.files.length; i++) {
newFiles.push(e.target.files[i]);
}
uploadObject(newFiles, "");
e.target.value = "";
};
const downloadObject = (object: BucketObjectItem) => {
const identityDownload = encodeURLString(
`${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
);
const downloadCall = download(
bucketName,
encodeURLString(object.name),
object.version_id,
object.size,
(progress) => {
dispatch(
updateProgress({
instanceID: identityDownload,
progress: progress,
})
);
},
() => {
dispatch(completeObject(identityDownload));
},
() => {
dispatch(failObject(identityDownload));
},
() => {
dispatch(cancelObjectInList(identityDownload));
}
);
const ID = makeid(8);
storeCallForObjectWithID(ID, downloadCall);
dispatch(
setNewObject({
ID,
bucketName,
done: false,
instanceID: identityDownload,
percentage: 0,
prefix: object.name,
type: "download",
waitingForFile: true,
failed: false,
cancelled: false,
})
);
downloadCall.send();
};
const openPath = (idElement: string) => {
setSelectedObjects([]);
const newPath = `/buckets/${bucketName}/browse${
idElement ? `/${encodeURLString(idElement)}` : ``
}`;
history.push(newPath);
dispatch(setObjectDetailsView(true));
dispatch(setLoadingVersions(true));
dispatch(
setSelectedObjectView(
`${idElement ? `${encodeURLString(idElement)}` : ``}`
)
);
};
const uploadObject = useCallback(
(files: File[], folderPath: string): void => {
let pathPrefix = "";
if (simplePath) {
pathPrefix = simplePath.endsWith("/") ? simplePath : simplePath + "/";
}
const upload = (
files: File[],
bucketName: string,
path: string,
folderPath: string
) => {
let uploadPromise = (file: File) => {
return new Promise((resolve, reject) => {
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
const fileName = file.name;
const blobFile = new Blob([file], { type: file.type });
let encodedPath = "";
const filePath = get(file, "path", "");
const fileWebkitRelativePath = get(file, "webkitRelativePath", "");
let relativeFolderPath = folderPath;
// File was uploaded via drag & drop
if (filePath !== "") {
relativeFolderPath = filePath;
} else if (fileWebkitRelativePath !== "") {
// File was uploaded using upload button
relativeFolderPath = fileWebkitRelativePath;
}
if (path !== "" || relativeFolderPath !== "") {
const finalFolderPath = relativeFolderPath
.split("/")
.slice(0, -1)
.join("/");
const pathClean = path.endsWith("/") ? path.slice(0, -1) : path;
encodedPath = encodeURLString(
`${pathClean}${
!pathClean.endsWith("/") &&
finalFolderPath !== "" &&
!finalFolderPath.startsWith("/")
? "/"
: ""
}${finalFolderPath}${
!finalFolderPath.endsWith("/") ||
(finalFolderPath.trim() === "" && !path.endsWith("/"))
? "/"
: ""
}`
);
}
if (encodedPath !== "") {
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
}
const identity = encodeURLString(
`${bucketName}-${encodedPath}-${new Date().getTime()}-${Math.random()}`
);
let xhr = new XMLHttpRequest();
xhr.open("POST", uploadUrl, true);
const areMultipleFiles = files.length > 1;
let errorMessage = `An error occurred while uploading the file${
areMultipleFiles ? "s" : ""
}.`;
const errorMessages: any = {
413: "Error - File size too large",
};
xhr.withCredentials = false;
xhr.onload = function (event) {
// resolve promise only when HTTP code is ok
if (xhr.status >= 200 && xhr.status < 300) {
dispatch(completeObject(identity));
resolve({ status: xhr.status });
} else {
// reject promise if there was a server error
if (errorMessages[xhr.status]) {
errorMessage = errorMessages[xhr.status];
} else if (xhr.response) {
try {
const err = JSON.parse(xhr.response);
errorMessage = err.detailedMessage;
} catch (e) {
errorMessage = "something went wrong";
}
}
dispatch(failObject(identity));
reject({ status: xhr.status, message: errorMessage });
}
};
xhr.upload.addEventListener("error", (event) => {
reject(errorMessage);
dispatch(failObject(identity));
return;
});
xhr.upload.addEventListener("progress", (event) => {
const progress = Math.floor((event.loaded * 100) / event.total);
dispatch(
updateProgress({
instanceID: identity,
progress: progress,
})
);
});
xhr.onerror = () => {
reject(errorMessage);
dispatch(failObject(identity));
return;
};
xhr.onloadend = () => {
if (files.length === 0) {
dispatch(setLoadingObjectsList(true));
}
};
xhr.onabort = () => {
dispatch(cancelObjectInList(identity));
};
const formData = new FormData();
if (file.size !== undefined) {
formData.append(file.size.toString(), blobFile, fileName);
const ID = makeid(8);
storeCallForObjectWithID(ID, xhr);
dispatch(
setNewObject({
ID,
bucketName,
done: false,
instanceID: identity,
percentage: 0,
prefix: `${decodeURLString(encodedPath)}${fileName}`,
type: "upload",
waitingForFile: false,
failed: false,
cancelled: false,
})
);
xhr.send(formData);
}
});
};
const uploadFilePromises: any = [];
// open object manager
dispatch(openList());
for (let i = 0; i < files.length; i++) {
const file = files[i];
uploadFilePromises.push(uploadPromise(file));
}
Promise.allSettled(uploadFilePromises).then((results: Array<any>) => {
const errors = results.filter(
(result) => result.status === "rejected"
);
if (errors.length > 0) {
const totalFiles = uploadFilePromises.length;
const successUploadedFiles =
uploadFilePromises.length - errors.length;
const err: ErrorResponseHandler = {
errorMessage: "There were some errors during file upload",
detailedError: `Uploaded files ${successUploadedFiles}/${totalFiles}`,
};
dispatch(setErrorSnackMessage(err));
}
// We force objects list reload after all promises were handled
dispatch(setLoadingObjectsList(true));
setSelectedObjects([]);
});
};
upload(files, bucketName, pathPrefix, folderPath);
},
[bucketName, dispatch, simplePath]
);
const onDrop = useCallback(
(acceptedFiles: any[]) => {
if (acceptedFiles && acceptedFiles.length > 0) {
let newFolderPath: string = acceptedFiles[0].path;
uploadObject(acceptedFiles, newFolderPath);
}
},
[uploadObject]
);
const { getRootProps, getInputProps, isDragActive, isDragAccept } =
useDropzone({
noClick: true,
onDrop,
});
const dndStyles = useMemo(
() => ({
...baseDnDStyle,
...(isDragActive ? activeDnDStyle : {}),
...(isDragAccept ? acceptDnDStyle : {}),
}),
[isDragActive, isDragAccept]
);
const openPreview = () => {
if (selectedObjects.length === 1) {
let fileObject: BucketObjectItem | undefined;
const findFunction = (currValue: BucketObjectItem) =>
selectedObjects.includes(currValue.name);
fileObject = filteredRecords.find(findFunction);
if (fileObject) {
setSelectedPreview(fileObject);
setPreviewOpen(true);
}
}
};
const openShare = () => {
if (selectedObjects.length === 1) {
let fileObject: BucketObjectItem | undefined;
const findFunction = (currValue: BucketObjectItem) =>
selectedObjects.includes(currValue.name);
fileObject = filteredRecords.find(findFunction);
if (fileObject) {
setSelectedPreview(fileObject);
setShareFileModalOpen(true);
}
}
};
const closeShareModal = () => {
setShareFileModalOpen(false);
setSelectedPreview(null);
};
const filteredRecords = records.filter((b: BucketObjectItem) => {
if (searchObjects === "") {
return true;
} else {
const objectName = b.name.toLowerCase();
if (objectName.indexOf(searchObjects.toLowerCase()) >= 0) {
return true;
} else {
return false;
}
}
});
const rewindCloseModal = () => {
setRewindSelect(false);
};
const closePreviewWindow = () => {
setPreviewOpen(false);
setSelectedPreview(null);
};
const selectListObjects = (e: React.ChangeEvent<HTMLInputElement>) => {
const targetD = e.target;
const value = targetD.value;
const checked = targetD.checked;
let elements: string[] = [...selectedObjects]; // We clone the selectedBuckets array
if (checked) {
// If the user has checked this field we need to push this to selectedBucketsList
elements.push(value);
} else {
// User has unchecked this field, we need to remove it from the list
elements = elements.filter((element) => element !== value);
}
setSelectedObjects(elements);
dispatch(setSelectedObjectView(null));
return elements;
};
const sortChange = (sortData: any) => {
const newSortDirection = get(sortData, "sortDirection", "DESC");
setCurrentSortField(sortData.sortBy);
setSortDirection(newSortDirection);
dispatch(setLoadingObjectsList(true));
};
const pageTitle = decodeURLString(internalPaths);
const currentPath = pageTitle.split("/").filter((i: string) => i !== "");
const plSelect = filteredRecords;
const sortASC = plSelect.sort(sortListObjects(currentSortField));
let payload: BucketObjectItem[] = [];
if (sortDirection === "ASC") {
payload = sortASC;
} else {
payload = sortASC.reverse();
}
const selectAllItems = () => {
dispatch(setSelectedObjectView(null));
if (selectedObjects.length === payload.length) {
setSelectedObjects([]);
return;
}
const elements = payload.map((item) => item.name);
setSelectedObjects(elements);
};
const downloadSelected = () => {
if (selectedObjects.length !== 0) {
let itemsToDownload: BucketObjectItem[] = [];
const filterFunction = (currValue: BucketObjectItem) =>
selectedObjects.includes(currValue.name);
itemsToDownload = filteredRecords.filter(filterFunction);
itemsToDownload.forEach((filteredItem) => {
downloadObject(filteredItem);
});
}
};
let uploadPath = [bucketName];
if (currentPath.length > 0) {
uploadPath = uploadPath.concat(currentPath);
}
const onClosePanel = (forceRefresh: boolean) => {
dispatch(setSelectedObjectView(null));
dispatch(setVersionsModeEnabled({ status: false }));
if (detailsOpen && selectedInternalPaths !== null) {
// We change URL to be the contained folder
const decodedPath = decodeURLString(internalPaths);
const splitURLS = decodedPath.split("/");
// We remove the last section of the URL as it should be a file
splitURLS.pop();
let URLItem = "";
if (splitURLS && splitURLS.length > 0) {
URLItem = `${splitURLS.join("/")}/`;
}
history.push(`/buckets/${bucketName}/browse/${encodeURLString(URLItem)}`);
}
dispatch(setObjectDetailsView(false));
setSelectedObjects([]);
if (forceRefresh) {
dispatch(setLoadingObjectsList(true));
}
};
const setDeletedAction = () => {
dispatch(setShowDeletedObjects(!showDeleted));
onClosePanel(true);
};
const tableActions: ItemActions[] = [
{
type: "view",
label: "View",
onClick: openPath,
sendOnlyId: true,
},
];
const multiActionButtons = [
{
action: downloadSelected,
label: "Download",
disabled: selectedObjects.length === 0,
icon: <DownloadIcon />,
tooltip: "Download Selected",
},
{
action: openShare,
label: "Share",
disabled: selectedObjects.length !== 1 || !canShareFile,
icon: <ShareIcon />,
tooltip: "Share Selected File",
},
{
action: openPreview,
label: "Preview",
disabled: selectedObjects.length !== 1 || !canPreviewFile,
icon: <PreviewIcon />,
tooltip: "Preview Selected File",
},
{
action: () => {
setDeleteMultipleOpen(true);
},
label: "Delete",
icon: <DeleteIcon />,
disabled:
!hasPermission(bucketName, [IAM_SCOPES.S3_DELETE_OBJECT]) ||
selectedObjects.length === 0 ||
!displayDeleteObject,
tooltip: "Delete Selected Files",
},
];
return (
<Fragment>
{shareFileModalOpen && selectedPreview && (
<ShareFile
open={shareFileModalOpen}
closeModalAndRefresh={closeShareModal}
bucketName={bucketName}
dataObject={{
name: selectedPreview.name,
last_modified: "",
version_id: selectedPreview.version_id,
}}
/>
)}
{deleteMultipleOpen && (
<DeleteMultipleObjects
deleteOpen={deleteMultipleOpen}
selectedBucket={bucketName}
selectedObjects={selectedObjects}
closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
versioning={isVersioned}
/>
)}
{rewindSelect && (
<RewindEnable
open={rewindSelect}
closeModalAndRefresh={rewindCloseModal}
bucketName={bucketName}
/>
)}
{previewOpen && (
<PreviewFileModal
open={previewOpen}
bucketName={bucketName}
object={selectedPreview}
onClosePreview={closePreviewWindow}
/>
)}
<PageLayout variant={"full"}>
<Grid item xs={12} className={classes.screenTitleContainer}>
<ScreenTitle
className={classes.screenTitle}
icon={
<span className={classes.listIcon}>
<BucketsIcon />
</span>
}
title={<span className={classes.titleSpacer}>{bucketName}</span>}
subTitle={
<Fragment>
<Grid item xs={12} className={classes.bucketDetails}>
<span className={classes.detailsSpacer}>
Created:
<strong>{bucketInfo?.creation_date || ""}</strong>
</span>
<span className={classes.detailsSpacer}>
Access:
<strong>{bucketInfo?.access || ""}</strong>
</span>
{bucketInfo && (
<Fragment>
<span className={classes.detailsSpacer}>
{bucketInfo.size && (
<Fragment>{niceBytesInt(bucketInfo.size)}</Fragment>
)}
{bucketInfo.size && quota && (
<Fragment> / {niceBytesInt(quota.quota)}</Fragment>
)}
{bucketInfo.size && bucketInfo.objects ? " - " : ""}
{bucketInfo.objects && (
<Fragment>
{bucketInfo.objects} Object
{bucketInfo.objects && bucketInfo.objects !== 1
? "s"
: ""}
</Fragment>
)}
</span>
</Fragment>
)}
</Grid>
</Fragment>
}
actions={
<Fragment>
<div className={classes.actionsSection}>
<RBIconButton
id={"rewind-objects-list"}
tooltip={"Rewind Bucket"}
text={"Rewind"}
icon={
<Badge
badgeContent=" "
color="secondary"
variant="dot"
invisible={!rewindEnabled}
className={classes.badgeOverlap}
sx={{ height: 16 }}
>
<HistoryIcon
style={{
minWidth: 16,
minHeight: 16,
width: 16,
height: 16,
}}
/>
</Badge>
}
color="primary"
variant={"outlined"}
onClick={() => {
setRewindSelect(true);
}}
disabled={
!isVersioned ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT])
}
/>
<RBIconButton
id={"refresh-objects-list"}
tooltip={"Reload List"}
text={"Refresh"}
icon={<RefreshIcon />}
color="primary"
variant={"outlined"}
onClick={() => {
if (versionsMode) {
dispatch(setLoadingVersions(true));
} else {
dispatch(setLoadingObjectsList(true));
}
}}
disabled={
!hasPermission(bucketName, [IAM_SCOPES.S3_LIST_BUCKET]) ||
rewindEnabled
}
/>
<input
type="file"
multiple
onChange={handleUploadButton}
style={{ display: "none" }}
ref={fileUpload}
/>
<input
type="file"
multiple
onChange={handleUploadButton}
style={{ display: "none" }}
ref={folderUpload}
/>
<UploadFilesButton
bucketName={bucketName}
uploadPath={uploadPath.join("/")}
uploadFileFunction={(closeMenu) => {
if (fileUpload && fileUpload.current) {
fileUpload.current.click();
}
closeMenu();
}}
uploadFolderFunction={(closeMenu) => {
if (folderUpload && folderUpload.current) {
folderUpload.current.click();
}
closeMenu();
}}
/>
</div>
</Fragment>
}
/>
</Grid>
<div
id="object-list-wrapper"
{...getRootProps({ style: { ...dndStyles } })}
>
<input {...getInputProps()} />
<Grid
item
xs={12}
className={classes.tableBlock}
sx={{ border: "#EAEDEE 1px solid", borderTop: 0 }}
>
{versionsMode ? (
<Fragment>
{selectedInternalPaths !== null && (
<VersionsNavigator
internalPaths={selectedInternalPaths}
bucketName={bucketName}
/>
)}
</Fragment>
) : (
<SecureComponent
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Grid item xs={12} className={classes.fullContainer}>
<Grid item xs={12} className={classes.breadcrumbsContainer}>
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={pageTitle}
existingFiles={records || []}
additionalOptions={
!isVersioned || rewindEnabled ? null : (
<div>
<CheckboxWrapper
name={"deleted_objects"}
id={"showDeletedObjects"}
value={"deleted_on"}
label={"Show deleted objects"}
onChange={setDeletedAction}
checked={showDeleted}
overrideLabelClasses={classes.labelStyle}
className={classes.overrideShowDeleted}
noTopMargin
/>
</div>
)
}
hidePathButton={false}
/>
</Grid>
<TableWrapper
itemActions={tableActions}
columns={
rewindEnabled ? rewindModeColumns : listModeColumns
}
isLoading={loading}
loadingMessage={loadingMessage}
entityName="Objects"
idField="name"
records={payload}
customPaperHeight={`${classes.browsePaper} ${
detailsOpen ? "actionsPanelOpen" : ""
}`}
selectedItems={selectedObjects}
onSelect={selectListObjects}
customEmptyMessage={`This location is empty${
!rewindEnabled ? ", please try uploading a new file" : ""
}`}
sortConfig={{
currentSort: currentSortField,
currentDirection: sortDirection,
triggerSort: sortChange,
}}
onSelectAll={selectAllItems}
rowStyle={({ index }) => {
if (payload[index]?.delete_flag) {
return "deleted";
}
return "";
}}
parentClassName={classes.parentWrapper}
/>
</Grid>
</SecureComponent>
)}
<SecureComponent
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<DetailsListPanel
open={detailsOpen}
closePanel={() => {
onClosePanel(false);
}}
className={`${versionsMode ? classes.hideListOnSmall : ""}`}
>
{selectedObjects.length > 0 && (
<ActionsListSection
items={multiActionButtons}
title={"Selected Objects:"}
/>
)}
{selectedInternalPaths !== null && (
<ObjectDetailPanel
internalPaths={selectedInternalPaths}
bucketName={bucketName}
onClosePanel={onClosePanel}
versioning={isVersioned}
locking={lockingEnabled}
/>
)}
</DetailsListPanel>
</SecureComponent>
</Grid>
</div>
</PageLayout>
</Fragment>
);
}