Skip to content

Commit 5000aaf

Browse files
bexsoftBenjamin Perez
andauthored
Added functionality for create folder & replaced icons (#368)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
1 parent b9f2a39 commit 5000aaf

File tree

7 files changed

+274
-20
lines changed

7 files changed

+274
-20
lines changed

portal-ui/src/icons/CreateIcon.tsx

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// This file is part of MinIO Console Server
2-
// Copyright (c) 2019 MinIO, Inc.
2+
// Copyright (c) 2020 MinIO, Inc.
33
//
44
// This program is free software: you can redistribute it and/or modify
55
// it under the terms of the GNU Affero General Public License as published by
@@ -15,21 +15,36 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import React from "react";
18-
import {SvgIcon} from "@material-ui/core";
18+
import { SvgIcon } from "@material-ui/core";
1919
class CreateIcon extends React.Component {
20-
render() {
21-
return (<SvgIcon>
22-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
23-
<title>ic_h_create-new_sl</title>
24-
<g id="Layer_2" data-name="Layer 2">
25-
<g id="Layer_1-2" data-name="Layer 1">
26-
<path className="cls-1"
27-
d="M0,0V16H16V0ZM11.886,9.048H9.048v2.838h-2.1V9.048H4.114v-2.1H6.952V4.114h2.1V6.952h2.838Z"/>
28-
</g>
29-
</g>
30-
</svg>
31-
</SvgIcon>)
32-
}
20+
render() {
21+
return (
22+
<SvgIcon>
23+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
24+
<g
25+
id="Group_55"
26+
data-name="Group 55"
27+
transform="translate(1002 -2555)"
28+
>
29+
<rect
30+
id="Rectangle_29"
31+
width="2"
32+
height="12"
33+
transform="translate(-997 2555)"
34+
fill="#fff"
35+
/>
36+
<rect
37+
id="Rectangle_30"
38+
width="2"
39+
height="12"
40+
transform="translate(-990 2560) rotate(90)"
41+
fill="#fff"
42+
/>
43+
</g>
44+
</svg>
45+
</SvgIcon>
46+
);
47+
}
3348
}
3449

3550
export default CreateIcon;

portal-ui/src/icons/UploadFile.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2020 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React from "react";
18+
import { SvgIcon } from "@material-ui/core";
19+
20+
class UploadFile extends React.Component {
21+
render() {
22+
return (
23+
<SvgIcon>
24+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12.996">
25+
<g transform="translate(-63.686 -70.783)">
26+
<path
27+
className="a"
28+
d="M74.736,79.879v1.95h-9.1v-1.95h-1.95v3.9h13v-3.9Z"
29+
/>
30+
<path
31+
className="a"
32+
d="M69.211,80.533h1.95V73.861h1.525l-2.5-3.078-2.5,3.078h1.525Z"
33+
/>
34+
</g>
35+
</svg>
36+
</SvgIcon>
37+
);
38+
}
39+
}
40+
41+
export default UploadFile;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2020 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React, { useState } from "react";
18+
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
19+
import { Button, Grid, LinearProgress } from "@material-ui/core";
20+
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
21+
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
22+
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
23+
import { connect } from "react-redux";
24+
import { createFolder } from "../../../../ObjectBrowser/actions";
25+
26+
interface ICreateFolder {
27+
classes: any;
28+
modalOpen: boolean;
29+
folderName: string;
30+
createFolder: (newFolder: string) => any;
31+
onClose: () => any;
32+
}
33+
34+
const styles = (theme: Theme) =>
35+
createStyles({
36+
buttonContainer: {
37+
textAlign: "right",
38+
},
39+
pathLabel: {
40+
marginTop: 0,
41+
marginBottom: 32,
42+
},
43+
...modalBasic,
44+
});
45+
46+
const CreateFolderModal = ({
47+
modalOpen,
48+
folderName,
49+
onClose,
50+
createFolder,
51+
classes,
52+
}: ICreateFolder) => {
53+
const [pathUrl, setPathUrl] = useState("");
54+
55+
const resetForm = () => {
56+
setPathUrl("");
57+
};
58+
59+
const createProcess = () => {
60+
createFolder(pathUrl);
61+
onClose();
62+
};
63+
64+
const folderTruncated = folderName.split("/").slice(2).join("/");
65+
66+
return (
67+
<React.Fragment>
68+
<ModalWrapper modalOpen={modalOpen} title="Add Folder" onClose={onClose}>
69+
<Grid container>
70+
<h3 className={classes.pathLabel}>
71+
Current Path: {folderTruncated}/
72+
</h3>
73+
<Grid item xs={12}>
74+
<InputBoxWrapper
75+
value={pathUrl}
76+
label={"Folder Path"}
77+
id={"folderPath"}
78+
name={"folderPath"}
79+
placeholder={"Enter Folder Path"}
80+
onChange={(e) => {
81+
setPathUrl(e.target.value);
82+
}}
83+
/>
84+
</Grid>
85+
<Grid item xs={12} className={classes.buttonContainer}>
86+
<button
87+
type="button"
88+
color="primary"
89+
className={classes.clearButton}
90+
onClick={resetForm}
91+
>
92+
Clear
93+
</button>
94+
<Button
95+
type="submit"
96+
variant="contained"
97+
color="primary"
98+
disabled={pathUrl.trim() === ""}
99+
onClick={createProcess}
100+
>
101+
Save
102+
</Button>
103+
</Grid>
104+
</Grid>
105+
</ModalWrapper>
106+
</React.Fragment>
107+
);
108+
};
109+
110+
const mapDispatchToProps = {
111+
createFolder,
112+
};
113+
114+
const connector = connect(null, mapDispatchToProps);
115+
116+
export default connector(withStyles(styles)(CreateFolderModal));

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ import { withRouter } from "react-router-dom";
4545
import { addRoute, setAllRoutes } from "../../../../ObjectBrowser/actions";
4646
import { connect } from "react-redux";
4747
import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers";
48+
import CreateFolderModal from "./CreateFolderModal";
49+
import { create } from "domain";
50+
import UploadFile from "../../../../../../icons/UploadFile";
4851

4952
const commonIcon = {
5053
backgroundRepeat: "no-repeat",
@@ -96,6 +99,11 @@ const styles = (theme: Theme) =>
9699
backgroundImage: "url(/images/ob_file_clear.svg)",
97100
...commonIcon,
98101
},
102+
buttonsContainer: {
103+
"& .MuiButtonBase-root": {
104+
marginLeft: 10,
105+
},
106+
},
99107
"@global": {
100108
".rowElementRaw:hover .iconFileElm": {
101109
backgroundImage: "url(/images/ob_file_filled.svg)",
@@ -134,6 +142,7 @@ const ListObjects = ({
134142
const [loading, setLoading] = useState<boolean>(true);
135143
const [error, setError] = useState<string>("");
136144
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
145+
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
137146
const [deleteError, setDeleteError] = useState<string>("");
138147
const [selectedObject, setSelectedObject] = useState<string>("");
139148
const [selectedBucket, setSelectedBucket] = useState<string>("");
@@ -182,6 +191,10 @@ const ListObjects = ({
182191
}
183192
};
184193

194+
const closeAddFolderModal = () => {
195+
setCreateFolderOpen(false);
196+
};
197+
185198
const showSnackBarMessage = (text: string) => {
186199
setSnackbarMessage(text);
187200
setOpenSnackbar(true);
@@ -310,10 +323,21 @@ const ListObjects = ({
310323
};
311324

312325
const uploadObject = (e: any): void => {
313-
// TODO: handle deeper paths/folders
326+
// Handle of deeper routes.
327+
const currentPath = routesList[routesList.length - 1].route;
328+
const splitPaths = currentPath
329+
.split("/")
330+
.filter((item) => item.trim() !== "");
331+
332+
let path = "";
333+
334+
if (splitPaths.length > 2) {
335+
path = `${splitPaths.slice(2).join("/")}/`;
336+
}
337+
314338
let file = e.target.files[0];
315339
showSnackBarMessage(`Uploading: ${file.name}`);
316-
upload(e, selectedBucket, "");
340+
upload(e, selectedBucket, path);
317341
};
318342

319343
const snackBarAction = (
@@ -375,6 +399,13 @@ const ListObjects = ({
375399
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
376400
/>
377401
)}
402+
{createFolderOpen && (
403+
<CreateFolderModal
404+
modalOpen={createFolderOpen}
405+
folderName={routesList[routesList.length - 1].route}
406+
onClose={closeAddFolderModal}
407+
/>
408+
)}
378409
<Snackbar
379410
open={openSnackbar}
380411
message={snackBarMessage}
@@ -387,14 +418,25 @@ const ListObjects = ({
387418
<div>
388419
<BrowserBreadcrumbs />
389420
</div>
390-
<div>
421+
<div className={classes.buttonsContainer}>
391422
<Button
392423
variant="contained"
393424
color="primary"
394425
startIcon={<CreateIcon />}
395426
component="label"
427+
onClick={() => {
428+
setCreateFolderOpen(true);
429+
}}
430+
>
431+
Create Folder
432+
</Button>
433+
<Button
434+
variant="contained"
435+
color="primary"
436+
startIcon={<UploadFile />}
437+
component="label"
396438
>
397-
Upload Object
439+
File
398440
<Input
399441
type="file"
400442
onChange={(e) => uploadObject(e)}

portal-ui/src/screens/Console/ObjectBrowser/actions.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const OBJECT_BROWSER_RESET_ROUTES_LIST =
2020
export const OBJECT_BROWSER_REMOVE_ROUTE_LEVEL =
2121
"OBJECT_BROWSER/REMOVE_ROUTE_LEVEL";
2222
export const OBJECT_BROWSER_SET_ALL_ROUTES = "OBJECT_BROWSER/SET_ALL_ROUTES";
23+
export const OBJECT_BROWSER_CREATE_FOLDER = "OBJECT_BROWSER/CREATE_FOLDER";
2324

2425
interface AddRouteAction {
2526
type: typeof OBJECT_BROWSER_ADD_ROUTE;
@@ -42,11 +43,17 @@ interface SetAllRoutes {
4243
currentRoute: string;
4344
}
4445

46+
interface CreateFolder {
47+
type: typeof OBJECT_BROWSER_CREATE_FOLDER;
48+
newRoute: string;
49+
}
50+
4551
export type ObjectBrowserActionTypes =
4652
| AddRouteAction
4753
| ResetRoutesList
4854
| RemoveRouteLevel
49-
| SetAllRoutes;
55+
| SetAllRoutes
56+
| CreateFolder;
5057

5158
export const addRoute = (route: string, label: string) => {
5259
return {
@@ -77,3 +84,10 @@ export const setAllRoutes = (currentRoute: string) => {
7784
currentRoute,
7885
};
7986
};
87+
88+
export const createFolder = (newRoute: string) => {
89+
return {
90+
type: OBJECT_BROWSER_CREATE_FOLDER,
91+
newRoute,
92+
};
93+
};

portal-ui/src/screens/Console/ObjectBrowser/reducers.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import history from "../../../history";
1717

1818
import {
1919
OBJECT_BROWSER_ADD_ROUTE,
20+
OBJECT_BROWSER_CREATE_FOLDER,
2021
OBJECT_BROWSER_REMOVE_ROUTE_LEVEL,
2122
OBJECT_BROWSER_RESET_ROUTES_LIST,
2223
OBJECT_BROWSER_SET_ALL_ROUTES,
@@ -85,6 +86,28 @@ export function objectBrowserReducer(
8586
...state,
8687
routesList: newSetOfRoutes,
8788
};
89+
case OBJECT_BROWSER_CREATE_FOLDER:
90+
const newFoldersRoutes = [...state.routesList];
91+
let lastRoute = state.routesList[state.routesList.length - 1].route;
92+
93+
const splitElements = action.newRoute.split("/");
94+
95+
splitElements.forEach((element) => {
96+
const folderTrim = element.trim();
97+
if (folderTrim !== "") {
98+
lastRoute = `${lastRoute}/${folderTrim}`;
99+
100+
const newItem = { route: lastRoute, label: folderTrim };
101+
newFoldersRoutes.push(newItem);
102+
}
103+
});
104+
105+
history.push(lastRoute);
106+
107+
return {
108+
...state,
109+
routesList: newFoldersRoutes,
110+
};
88111
default:
89112
return state;
90113
}

0 commit comments

Comments
 (0)