Added functionality for create folder & replaced icons (#368)

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2020-11-02 23:45:47 -06:00
committed by GitHub
parent b9f2a39d50
commit 5000aafba6
7 changed files with 274 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server // This file is part of MinIO Console Server
// Copyright (c) 2019 MinIO, Inc. // Copyright (c) 2020 MinIO, Inc.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
@@ -15,21 +15,36 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react"; import React from "react";
import {SvgIcon} from "@material-ui/core"; import { SvgIcon } from "@material-ui/core";
class CreateIcon extends React.Component { class CreateIcon extends React.Component {
render() { render() {
return (<SvgIcon> return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> <SvgIcon>
<title>ic_h_create-new_sl</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
<g id="Layer_2" data-name="Layer 2"> <g
<g id="Layer_1-2" data-name="Layer 1"> id="Group_55"
<path className="cls-1" data-name="Group 55"
d="M0,0V16H16V0ZM11.886,9.048H9.048v2.838h-2.1V9.048H4.114v-2.1H6.952V4.114h2.1V6.952h2.838Z"/> transform="translate(1002 -2555)"
</g> >
</g> <rect
</svg> id="Rectangle_29"
</SvgIcon>) width="2"
} height="12"
transform="translate(-997 2555)"
fill="#fff"
/>
<rect
id="Rectangle_30"
width="2"
height="12"
transform="translate(-990 2560) rotate(90)"
fill="#fff"
/>
</g>
</svg>
</SvgIcon>
);
}
} }
export default CreateIcon; export default CreateIcon;

View File

@@ -0,0 +1,41 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 React from "react";
import { SvgIcon } from "@material-ui/core";
class UploadFile extends React.Component {
render() {
return (
<SvgIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12.996">
<g transform="translate(-63.686 -70.783)">
<path
className="a"
d="M74.736,79.879v1.95h-9.1v-1.95h-1.95v3.9h13v-3.9Z"
/>
<path
className="a"
d="M69.211,80.533h1.95V73.861h1.525l-2.5-3.078-2.5,3.078h1.525Z"
/>
</g>
</svg>
</SvgIcon>
);
}
}
export default UploadFile;

View File

@@ -0,0 +1,116 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 React, { useState } from "react";
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
import { Button, Grid, LinearProgress } from "@material-ui/core";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
import { connect } from "react-redux";
import { createFolder } from "../../../../ObjectBrowser/actions";
interface ICreateFolder {
classes: any;
modalOpen: boolean;
folderName: string;
createFolder: (newFolder: string) => any;
onClose: () => any;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
pathLabel: {
marginTop: 0,
marginBottom: 32,
},
...modalBasic,
});
const CreateFolderModal = ({
modalOpen,
folderName,
onClose,
createFolder,
classes,
}: ICreateFolder) => {
const [pathUrl, setPathUrl] = useState("");
const resetForm = () => {
setPathUrl("");
};
const createProcess = () => {
createFolder(pathUrl);
onClose();
};
const folderTruncated = folderName.split("/").slice(2).join("/");
return (
<React.Fragment>
<ModalWrapper modalOpen={modalOpen} title="Add Folder" onClose={onClose}>
<Grid container>
<h3 className={classes.pathLabel}>
Current Path: {folderTruncated}/
</h3>
<Grid item xs={12}>
<InputBoxWrapper
value={pathUrl}
label={"Folder Path"}
id={"folderPath"}
name={"folderPath"}
placeholder={"Enter Folder Path"}
onChange={(e) => {
setPathUrl(e.target.value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={pathUrl.trim() === ""}
onClick={createProcess}
>
Save
</Button>
</Grid>
</Grid>
</ModalWrapper>
</React.Fragment>
);
};
const mapDispatchToProps = {
createFolder,
};
const connector = connect(null, mapDispatchToProps);
export default connector(withStyles(styles)(CreateFolderModal));

View File

@@ -45,6 +45,9 @@ import { withRouter } from "react-router-dom";
import { addRoute, setAllRoutes } from "../../../../ObjectBrowser/actions"; import { addRoute, setAllRoutes } from "../../../../ObjectBrowser/actions";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers"; import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers";
import CreateFolderModal from "./CreateFolderModal";
import { create } from "domain";
import UploadFile from "../../../../../../icons/UploadFile";
const commonIcon = { const commonIcon = {
backgroundRepeat: "no-repeat", backgroundRepeat: "no-repeat",
@@ -96,6 +99,11 @@ const styles = (theme: Theme) =>
backgroundImage: "url(/images/ob_file_clear.svg)", backgroundImage: "url(/images/ob_file_clear.svg)",
...commonIcon, ...commonIcon,
}, },
buttonsContainer: {
"& .MuiButtonBase-root": {
marginLeft: 10,
},
},
"@global": { "@global": {
".rowElementRaw:hover .iconFileElm": { ".rowElementRaw:hover .iconFileElm": {
backgroundImage: "url(/images/ob_file_filled.svg)", backgroundImage: "url(/images/ob_file_filled.svg)",
@@ -134,6 +142,7 @@ const ListObjects = ({
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const [deleteOpen, setDeleteOpen] = useState<boolean>(false); const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
const [deleteError, setDeleteError] = useState<string>(""); const [deleteError, setDeleteError] = useState<string>("");
const [selectedObject, setSelectedObject] = useState<string>(""); const [selectedObject, setSelectedObject] = useState<string>("");
const [selectedBucket, setSelectedBucket] = useState<string>(""); const [selectedBucket, setSelectedBucket] = useState<string>("");
@@ -182,6 +191,10 @@ const ListObjects = ({
} }
}; };
const closeAddFolderModal = () => {
setCreateFolderOpen(false);
};
const showSnackBarMessage = (text: string) => { const showSnackBarMessage = (text: string) => {
setSnackbarMessage(text); setSnackbarMessage(text);
setOpenSnackbar(true); setOpenSnackbar(true);
@@ -310,10 +323,21 @@ const ListObjects = ({
}; };
const uploadObject = (e: any): void => { const uploadObject = (e: any): void => {
// TODO: handle deeper paths/folders // Handle of deeper routes.
const currentPath = routesList[routesList.length - 1].route;
const splitPaths = currentPath
.split("/")
.filter((item) => item.trim() !== "");
let path = "";
if (splitPaths.length > 2) {
path = `${splitPaths.slice(2).join("/")}/`;
}
let file = e.target.files[0]; let file = e.target.files[0];
showSnackBarMessage(`Uploading: ${file.name}`); showSnackBarMessage(`Uploading: ${file.name}`);
upload(e, selectedBucket, ""); upload(e, selectedBucket, path);
}; };
const snackBarAction = ( const snackBarAction = (
@@ -375,6 +399,13 @@ const ListObjects = ({
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh} closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
/> />
)} )}
{createFolderOpen && (
<CreateFolderModal
modalOpen={createFolderOpen}
folderName={routesList[routesList.length - 1].route}
onClose={closeAddFolderModal}
/>
)}
<Snackbar <Snackbar
open={openSnackbar} open={openSnackbar}
message={snackBarMessage} message={snackBarMessage}
@@ -387,14 +418,25 @@ const ListObjects = ({
<div> <div>
<BrowserBreadcrumbs /> <BrowserBreadcrumbs />
</div> </div>
<div> <div className={classes.buttonsContainer}>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
startIcon={<CreateIcon />} startIcon={<CreateIcon />}
component="label" component="label"
onClick={() => {
setCreateFolderOpen(true);
}}
> >
Upload Object Create Folder
</Button>
<Button
variant="contained"
color="primary"
startIcon={<UploadFile />}
component="label"
>
File
<Input <Input
type="file" type="file"
onChange={(e) => uploadObject(e)} onChange={(e) => uploadObject(e)}

View File

@@ -20,6 +20,7 @@ export const OBJECT_BROWSER_RESET_ROUTES_LIST =
export const OBJECT_BROWSER_REMOVE_ROUTE_LEVEL = export const OBJECT_BROWSER_REMOVE_ROUTE_LEVEL =
"OBJECT_BROWSER/REMOVE_ROUTE_LEVEL"; "OBJECT_BROWSER/REMOVE_ROUTE_LEVEL";
export const OBJECT_BROWSER_SET_ALL_ROUTES = "OBJECT_BROWSER/SET_ALL_ROUTES"; export const OBJECT_BROWSER_SET_ALL_ROUTES = "OBJECT_BROWSER/SET_ALL_ROUTES";
export const OBJECT_BROWSER_CREATE_FOLDER = "OBJECT_BROWSER/CREATE_FOLDER";
interface AddRouteAction { interface AddRouteAction {
type: typeof OBJECT_BROWSER_ADD_ROUTE; type: typeof OBJECT_BROWSER_ADD_ROUTE;
@@ -42,11 +43,17 @@ interface SetAllRoutes {
currentRoute: string; currentRoute: string;
} }
interface CreateFolder {
type: typeof OBJECT_BROWSER_CREATE_FOLDER;
newRoute: string;
}
export type ObjectBrowserActionTypes = export type ObjectBrowserActionTypes =
| AddRouteAction | AddRouteAction
| ResetRoutesList | ResetRoutesList
| RemoveRouteLevel | RemoveRouteLevel
| SetAllRoutes; | SetAllRoutes
| CreateFolder;
export const addRoute = (route: string, label: string) => { export const addRoute = (route: string, label: string) => {
return { return {
@@ -77,3 +84,10 @@ export const setAllRoutes = (currentRoute: string) => {
currentRoute, currentRoute,
}; };
}; };
export const createFolder = (newRoute: string) => {
return {
type: OBJECT_BROWSER_CREATE_FOLDER,
newRoute,
};
};

View File

@@ -17,6 +17,7 @@ import history from "../../../history";
import { import {
OBJECT_BROWSER_ADD_ROUTE, OBJECT_BROWSER_ADD_ROUTE,
OBJECT_BROWSER_CREATE_FOLDER,
OBJECT_BROWSER_REMOVE_ROUTE_LEVEL, OBJECT_BROWSER_REMOVE_ROUTE_LEVEL,
OBJECT_BROWSER_RESET_ROUTES_LIST, OBJECT_BROWSER_RESET_ROUTES_LIST,
OBJECT_BROWSER_SET_ALL_ROUTES, OBJECT_BROWSER_SET_ALL_ROUTES,
@@ -85,6 +86,28 @@ export function objectBrowserReducer(
...state, ...state,
routesList: newSetOfRoutes, routesList: newSetOfRoutes,
}; };
case OBJECT_BROWSER_CREATE_FOLDER:
const newFoldersRoutes = [...state.routesList];
let lastRoute = state.routesList[state.routesList.length - 1].route;
const splitElements = action.newRoute.split("/");
splitElements.forEach((element) => {
const folderTrim = element.trim();
if (folderTrim !== "") {
lastRoute = `${lastRoute}/${folderTrim}`;
const newItem = { route: lastRoute, label: folderTrim };
newFoldersRoutes.push(newItem);
}
});
history.push(lastRoute);
return {
...state,
routesList: newFoldersRoutes,
};
default: default:
return state; return state;
} }

View File

@@ -79,6 +79,9 @@ const theme = createMuiTheme({
fontWeight: 600, fontWeight: 600,
color: "#767676", color: "#767676",
}, },
"& .MuiButton-iconSizeMedium > *:first-child": {
fontSize: 12,
},
}, },
}, },
}, },