Changed breadcrumbs styles (#1610)

- Added onKey press support to input boxes

- Added enter key support to create path modal

- Replaced bucket icon

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2022-02-22 10:14:55 -07:00
committed by GitHub
parent 844162a7ab
commit c15d75e619
10 changed files with 215 additions and 138 deletions

View File

@@ -0,0 +1,37 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as React from "react";
import { SVGProps } from "react";
const BackCaretIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className={`min-icon`}
fill={"currentcolor"}
viewBox="0 0 256 256"
{...props}
>
<g id="noun_chevron_2320228" transform="translate(5.595 10) rotate(180)">
<path id="Path_6842" d="M-178.01,7.8c-3.9-0.03-7.62-1.63-10.34-4.43c-5.81-5.68-5.92-15-0.25-20.81
c0.08-0.08,0.16-0.16,0.25-0.25l100.13-100.13l-100.13-100.48c-5.81-5.68-5.92-15-0.25-20.81c0.08-0.08,0.16-0.16,0.25-0.25
c5.68-5.81,15-5.92,20.81-0.25c0.08,0.08,0.16,0.16,0.25,0.25l110.82,110.82c2.8,2.72,4.39,6.44,4.43,10.34
c0.11,3.93-1.51,7.71-4.43,10.34L-167.29,2.99C-170.07,5.97-173.93,7.71-178.01,7.8z"/>
</g>
</svg>
);
export default BackCaretIcon;

View File

@@ -25,19 +25,12 @@ const BucketsIcon = (props: SVGProps<SVGSVGElement>) => (
viewBox="0 0 256 256"
{...props}
>
<defs>
<clipPath id="prefix__a">
<path d="M0 0h256v256H0z" />
</clipPath>
</defs>
<g clipPath="url(#prefix__a)">
<path fill="none" d="M0 0h256v256H0z" />
<path data-name="Rect\xE1ngulo 884" fill="none" d="M0 0h256v256H0z" />
<g>
<path
data-name="buckets-icn"
d="m244.998 90.474.049-.243 10.563-61.472a25.8 25.8 0 0 0-4.748-19.986A21.518 21.518 0 0 0 233.739 0H22.255A21.507 21.507 0 0 0 5.138 8.773 25.862 25.862 0 0 0 .384 28.759c5.223 30.384 16.209 94.421 25 145.533l.014.1c4.457 26 8.338 48.644 10.617 61.787 1.965 11.487 11.148 19.819 21.854 19.819h140.264c10.713 0 19.875-8.332 21.861-19.819l10.592-61.711.076-.375Zm-203.928 72.5c-3.6-20.981-7.479-43.648-11.148-65.015H226.09l-11.168 65.015Zm197.482-137.7-9.2 53.735h-202.7c-3.7-21.626-7-40.758-9.221-53.735a5.736 5.736 0 0 1 1.041-4.394 4.738 4.738 0 0 1 3.764-1.934h211.5a4.732 4.732 0 0 1 3.775 1.939 5.691 5.691 0 0 1 1.042 4.387Zm-26.893 156.649-8.709 50.763a5.048 5.048 0 0 1-4.824 4.37H57.862a5.047 5.047 0 0 1-4.816-4.361c-1.93-11.25-5.066-29.464-8.717-50.771Z"
stroke="#000"
strokeWidth={0.2}
d="M244.1,8.4c-3.9-5.3-10.1-8.5-16.7-8.5H21.6C15,0,8.8,3.1,4.9,8.4C0.8,14-0.9,21,0.3,27.9
c5.1,29.6,15.8,91.9,24.3,141.7v0.1C29,195,32.8,217.1,35,229.9c1.4,10.8,10.4,18.9,21.3,19.3h136.5
c10.9-0.4,19.9-8.5,21.3-19.3l10.3-60.1l0.1-0.4L238.4,88v-0.2l10.3-59.9C249.9,21,248.3,14,244.1,8.4 M206.1,177h-163
l-3.2-18.6h169.3L206.1,177z M220,95.3H28.9l-3.2-18.6h197.4L220,95.3z"
/>
</g>
</svg>

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -25,17 +25,11 @@ const FolderIcon = (props: SVGProps<SVGSVGElement>) => (
viewBox="0 0 256 256"
{...props}
>
<defs>
<clipPath id="prefix__a">
<path d="M0 0h256v256H0z" />
</clipPath>
</defs>
<g clipPath="url(#prefix__a)">
<path fill="none" d="M0 0h256v256H0z" />
<g data-name="folder-icn{">
<path d="M101.729 42.952c6.612 0 13.7 18.758 20.78 18.758h88.049a9.441 9.441 0 0 1 9.444 9.379v4.689H40.349V52.33h-.236a9.441 9.441 0 0 1 9.448-9.378h52.168m124.4 44.255a9.778 9.778 0 0 1 9.774 9.725L225.885 204.09a9.788 9.788 0 0 1-9.794 9.716H39.679a9.781 9.781 0 0 1-9.786-9.716l-9.79-107.158a9.781 9.781 0 0 1 9.79-9.725h196.236M101.729 23H49.561a29.469 29.469 0 0 0-29.544 29.33 20.109 20.109 0 0 0 .236 3.063v13.438A29.758 29.758 0 0 0 0 96.931c0 .6.031 1.2.09 1.8l9.723 106.5a29.827 29.827 0 0 0 29.862 28.532h176.412a29.833 29.833 0 0 0 29.862-28.5l9.959-106.484a17.091 17.091 0 0 0 .091-1.847 29.673 29.673 0 0 0-15.911-26.229 29.477 29.477 0 0 0-29.532-28.949h-81.5c-.4-.529-.787-1.05-1.117-1.5-5.1-6.87-12.815-17.248-26.208-17.248Z" />
<path data-name="Rect\xE1ngulo 874" fill="none" d="M0 0h256v256H0z" />
</g>
<g>
<path d="M235.3,72.5c-0.2-15.5-12.8-27.9-28.3-27.9h-78l-1.1-1.5c-5.1-9.3-14.5-15.5-25.1-16.6h-50c-15.6,0-28.3,12.6-28.3,28.3
c0,1,0.1,2,0.2,3v12.9c-11.6,3.9-19.4,14.8-19.4,27c0,0.6,0,1.2,0.1,1.7L14.8,202c0.6,15.4,13.2,27.5,28.6,27.5h168.9
c15.4,0,28-12.1,28.6-27.5l9.5-102.5c0-0.6,0.1-1.2,0.1-1.8C250.6,87.1,244.7,77.4,235.3,72.5z M32.5,88.4c11.7-3.3,12-11,12-11
h172c0.2,4.6,2.9,8.8,6.9,11H32.5z"/>
</g>
</svg>
);

View File

@@ -166,3 +166,4 @@ export { default as ConsoleIcon } from "./ConsoleIcon";
export { default as FileVideoIcon } from "./FileVideoIcon";
export { default as ChangePasswordIcon } from "./ChangePasswordIcon";
export { default as LockIcon } from "./LockIcon";
export { default as BackCaretIcon } from "./BackCaretIcon";

View File

@@ -102,6 +102,16 @@ const CreateFolderModal = ({
setIsFormValid(valid);
}, [pathUrl]);
const inputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPathUrl(e.target.value);
};
const keyPressed = (e: any) => {
if(e.code === "Enter" && pathUrl !== "") {
createProcess();
}
};
return (
<React.Fragment>
<ModalWrapper
@@ -121,9 +131,8 @@ const CreateFolderModal = ({
id={"folderPath"}
name={"folderPath"}
placeholder={"Enter the new Folder Path"}
onChange={(e) => {
setPathUrl(e.target.value);
}}
onChange={inputChange}
onKeyPress={keyPressed}
required
/>
</Grid>

View File

@@ -102,9 +102,6 @@ import ObjectDetailPanel from "./ObjectDetailPanel";
import RBIconButton from "../../../BucketDetails/SummaryItems/RBIconButton";
import MultiSelectionPanel from "./MultiSelectionPanel";
const AddFolderIcon = React.lazy(
() => import("../../../../../../icons/AddFolderIcon")
);
const HistoryIcon = React.lazy(
() => import("../../../../../../icons/HistoryIcon")
);
@@ -115,9 +112,7 @@ const RefreshIcon = React.lazy(
const DeleteIcon = React.lazy(
() => import("../../../../../../icons/DeleteIcon")
);
const CreateFolderModal = withSuspense(
React.lazy(() => import("./CreateFolderModal"))
);
const DeleteMultipleObjects = withSuspense(
React.lazy(() => import("./DeleteMultipleObjects"))
);
@@ -169,6 +164,22 @@ const styles = (theme: Theme) =>
...searchField.searchField,
maxWidth: 380,
},
screenTitleContainer: {
border: "#EAEDEE 1px solid",
borderBottom: 0,
padding: "0.8rem 15px 0",
},
titleSpacer: {
marginLeft: 10,
},
listIcon: {
display: "block",
marginTop: "-10px",
"& .min-icon": {
width: 20,
height: 20,
}
},
...objectBrowserCommon,
...containerForHeader(theme.spacing(4)),
});
@@ -269,7 +280,6 @@ const ListObjects = ({
const [rewind, setRewind] = useState<RewindObject[]>([]);
const [loadingRewind, setLoadingRewind] = useState<boolean>(false);
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
const [loadingStartTime, setLoadingStartTime] = useState<number>(0);
const [loadingMessage, setLoadingMessage] =
useState<React.ReactNode>(defLoading);
@@ -372,12 +382,12 @@ const ListObjects = ({
if (timeDelta / 1000 >= 6) {
setLoadingMessage(
<React.Fragment>
<Fragment>
<Typography component="h3">
This operation is taking longer than expected... (
{Math.ceil(timeDelta / 1000)}s)
</Typography>
</React.Fragment>
</Fragment>
);
} else if (timeDelta / 1000 >= 3) {
setLoadingMessage(
@@ -656,9 +666,7 @@ const ListObjects = ({
}
};
const closeAddFolderModal = () => {
setCreateFolderOpen(false);
};
const handleUploadButton = (e: any) => {
if (
@@ -1167,7 +1175,7 @@ const ListObjects = ({
];
return (
<React.Fragment>
<Fragment>
{shareFileModalOpen && selectedPreview && (
<ShareFile
open={shareFileModalOpen}
@@ -1189,15 +1197,6 @@ const ListObjects = ({
versioning={isVersioned}
/>
)}
{createFolderOpen && (
<CreateFolderModal
modalOpen={createFolderOpen}
bucketName={bucketName}
folderName={internalPaths}
onClose={closeAddFolderModal}
existingFiles={records}
/>
)}
{rewindSelect && (
<RewindEnable
open={rewindSelect}
@@ -1214,21 +1213,15 @@ const ListObjects = ({
/>
)}
<PageLayout>
<Grid item xs={12}>
<Grid item xs={12} className={classes.screenTitleContainer}>
<ScreenTitle
className={classes.screenTitle}
icon={
<Fragment>
<BucketsIcon width={40} />
</Fragment>
}
title={
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={pageTitle}
fullSizeBreadcrumbs
/>
<span className={classes.listIcon}>
<BucketsIcon />
</span>
}
title={<span className={classes.titleSpacer}>{bucketName}</span>}
subTitle={
<Fragment>
<Grid item xs={12} className={classes.bucketDetails}>
@@ -1266,21 +1259,6 @@ const ListObjects = ({
}
actions={
<Fragment>
<RBIconButton
id={"new-path"}
tooltip={"Choose or create a new path"}
text={"New Path"}
icon={<AddFolderIcon />}
color="primary"
variant={"outlined"}
onClick={() => {
setCreateFolderOpen(true);
}}
disabled={
rewindEnabled ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT])
}
/>
<RBIconButton
id={"rewind-objects-list"}
tooltip={"Rewind Bucket"}
@@ -1292,6 +1270,7 @@ const ListObjects = ({
variant="dot"
invisible={!rewindEnabled}
className={classes.badgeOverlap}
sx={{height: 12}}
>
<HistoryIcon />
</Badge>
@@ -1355,6 +1334,13 @@ const ListObjects = ({
}
/>
</Grid>
<Grid item xs={12}>
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={pageTitle}
existingFiles={records || []}
/>
</Grid>
<div
id="object-list-wrapper"
{...getRootProps({ style: { ...dndStyles } })}
@@ -1414,7 +1400,7 @@ const ListObjects = ({
</Grid>
</div>
</PageLayout>
</React.Fragment>
</Fragment>
);
};

View File

@@ -48,7 +48,6 @@ import {
import { decodeFileName, encodeFileName } from "../../../../../../common/utils";
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
import SetRetention from "./SetRetention";
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
import DeleteObject from "../ListObjects/DeleteObject";
import AddTagModal from "./AddTagModal";
import DeleteTagModal from "./DeleteTagModal";
@@ -518,14 +517,6 @@ const ObjectDetails = ({
? objectNameArray[objectNameArray.length - 1]
: actualInfo.name
}
subTitle={
<Fragment>
<BrowserBreadcrumbs
bucketName={bucketName}
internalPaths={actualInfo.name}
/>
</Fragment>
}
actions={
<Fragment>
<SecureComponent

View File

@@ -40,6 +40,7 @@ interface InputBoxProps {
label: string;
classes: any;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onKeyPress?: (e: any) => void;
value: string | boolean;
id: string;
name: string;
@@ -133,6 +134,7 @@ const InputBoxWrapper = ({
autoFocus = false,
classes,
className = "",
onKeyPress,
}: InputBoxProps) => {
let inputProps: any = { "data-index": index, ...extraInputProps };
@@ -197,6 +199,7 @@ const InputBoxWrapper = ({
helperText={error}
placeholder={placeholder}
className={classes.inputRebase}
onKeyPress={onKeyPress}
/>
{overlayIcon && (
<div

View File

@@ -361,20 +361,27 @@ export const objectBrowserCommon = {
lineHeight: "40px",
},
breadcrumbs: {
fontSize: 10,
color: "#000",
fontSize: 12,
color: "#969FA8",
fontWeight: "bold",
marginTop: 2,
border: "#EAEDEE 1px solid",
borderBottom: 0,
height: 38,
display: "flex",
alignItems: "center",
backgroundColor: "#FCFCFD",
"& a": {
textDecoration: "none",
color: "#000",
color: "#969FA8",
"&:hover": {
textDecoration: "underline",
},
},
"&.fullSize": {
fontSize: 18,
marginLeft: 10,
},
"& .min-icon": {
width: 14,
minWidth: 14,
}
},
smallLabel: {
color: "#9C9C9C",
@@ -388,6 +395,17 @@ export const objectBrowserCommon = {
detailsSpacer: {
marginRight: 18,
},
breadcrumbsList: {
textOverflow: "ellipsis" as const,
overflow: "hidden" as const,
whiteSpace: "nowrap" as const,
display: "inline-block" as const,
flexGrow: 1,
textAlign: "left" as const,
marginLeft: 15,
marginRight: 10,
lineHeight: 35,
},
};
export const selectorsCommon = {

View File

@@ -14,10 +14,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import React, { Fragment, useState } from "react";
import get from "lodash/get";
import Grid from "@mui/material/Grid";
import Moment from "react-moment";
import { connect } from "react-redux";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
@@ -26,6 +25,19 @@ import { ObjectBrowserState } from "./reducers";
import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary";
import { Link } from "react-router-dom";
import { encodeFileName } from "../../../common/utils";
import { BackCaretIcon, FolderIcon } from "../../../icons";
import { IconButton, Tooltip } from "@mui/material";
import history from "../../../history";
import { hasPermission } from "../../../common/SecureComponent";
import { IAM_SCOPES } from "../../../common/SecureComponent/permissions";
import withSuspense from "../Common/Components/withSuspense";
import { BucketObject } from "../Buckets/ListBuckets/Objects/ListObjects/types";
const CreateFolderModal = withSuspense(
React.lazy(
() => import("../Buckets/ListBuckets/Objects/ListObjects/CreateFolderModal")
)
);
interface ObjectBrowserReducer {
objectBrowser: ObjectBrowserState;
@@ -37,7 +49,7 @@ interface IObjectBrowser {
internalPaths: string;
rewindEnabled?: boolean;
rewindDate?: any;
fullSizeBreadcrumbs?: boolean;
existingFiles: BucketObject[];
}
const styles = (theme: Theme) =>
@@ -51,8 +63,10 @@ const BrowserBreadcrumbs = ({
internalPaths,
rewindEnabled,
rewindDate,
fullSizeBreadcrumbs,
existingFiles,
}: IObjectBrowser) => {
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
let paths = internalPaths;
if (internalPaths !== "") {
@@ -60,52 +74,83 @@ const BrowserBreadcrumbs = ({
}
const splitPaths = paths.split("/").filter((path) => path !== "");
const listBreadcrumbs = splitPaths.map(
(objectItem: string, index: number) => {
const subSplit = splitPaths.slice(0, index + 1).join("/");
const route = `/buckets/${bucketName}/browse/${
subSplit ? `${encodeFileName(subSplit)}` : ``
}`;
return (
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
<Link to={route}>{objectItem}</Link>
{index < splitPaths.length - 1 && <span> / </span>}
</React.Fragment>
);
}
);
let breadcrumbsMap = splitPaths.map((objectItem: string, index: number) => {
const subSplit = splitPaths.slice(0, index + 1).join("/");
const route = `/buckets/${bucketName}/browse/${
subSplit ? `${encodeFileName(subSplit)}` : ``
}`;
return (
<Fragment key={`breadcrumbs-${index.toString()}`}>
<span> / </span>
<Link to={route}>{objectItem}</Link>
</Fragment>
);
});
const listBreadcrumbs: any[] = [
<Fragment key={`breadcrumbs-root-path`}>
<Link to={`/buckets/${bucketName}/browse`}>{bucketName}</Link>
</Fragment>,
...breadcrumbsMap,
];
const closeAddFolderModal = () => {
setCreateFolderOpen(false);
};
const title = false;
return (
<React.Fragment>
{title && (
<Grid item xs={12}>
<div className={classes.sectionTitle}>
{splitPaths && splitPaths.length > 0
? splitPaths[splitPaths.length - 1]
: ""}
{rewindEnabled && splitPaths.length > 1 && (
<small className={classes.smallLabel}>
&nbsp;(Rewind:{" "}
<Moment date={rewindDate} format="MMMM Do YYYY, h:mm a" /> )
</small>
)}
</div>
</Grid>
{createFolderOpen && (
<CreateFolderModal
modalOpen={createFolderOpen}
bucketName={bucketName}
folderName={internalPaths}
onClose={closeAddFolderModal}
existingFiles={existingFiles}
/>
)}
<Grid
item
xs={12}
className={`${classes.breadcrumbs} ${
fullSizeBreadcrumbs ? "fullSize" : ""
}`}
>
<React.Fragment>
<Link to={`/buckets/${bucketName}/browse`}>{bucketName}</Link>
{listBreadcrumbs.length > 0 && <span> / </span>}
</React.Fragment>
{listBreadcrumbs}
<Grid item xs={12} className={`${classes.breadcrumbs}`}>
<IconButton
onClick={() => {
history.goBack();
}}
sx={{
border: "#EAEDEE 1px solid",
backgroundColor: "#fff",
borderLeft: 0,
borderBottom: 0,
borderRadius: 0,
width: 39,
height: 39,
marginRight: "10px",
}}
>
<BackCaretIcon />
</IconButton>
<Tooltip title={"Choose or create a new path"}>
<IconButton
id={"new-path"}
onClick={() => {
setCreateFolderOpen(true);
}}
disabled={
rewindEnabled ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT])
}
disableTouchRipple
disableRipple
focusRipple={false}
sx={{
padding: 0,
paddingLeft: "6px",
}}
>
<FolderIcon />
</IconButton>
</Tooltip>
<div className={classes.breadcrumbsList} dir="rtl">
{listBreadcrumbs}
</div>
</Grid>
</React.Fragment>
);