Skip to content

Commit 8658ac2

Browse files
authored
Improved the Share File modal (#1083)
1 parent 19f034a commit 8658ac2

File tree

5 files changed

+299
-50
lines changed

5 files changed

+299
-50
lines changed

portal-ui/src/index.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ code {
99
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
1010
monospace;
1111
}
12+
13+
/* Chrome, Safari, Edge, Opera */
14+
input.removeArrows::-webkit-outer-spin-button,
15+
input.removeArrows::-webkit-inner-spin-button {
16+
-webkit-appearance: none;
17+
margin: 0;
18+
}
19+
20+
/* Firefox */
21+
input.removeArrows[type=number] {
22+
-moz-appearance: textfield;
23+
}

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

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import { AppState } from "../../../../../../store";
3232
import { ErrorResponseHandler } from "../../../../../../common/types";
3333
import api from "../../../../../../common/api";
3434
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
35-
import DateSelector from "../../../../Common/FormComponents/DateSelector/DateSelector";
3635
import PredefinedList from "../../../../Common/FormComponents/PredefinedList/PredefinedList";
36+
import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector";
3737

3838
const styles = (theme: Theme) =>
3939
createStyles({
@@ -72,68 +72,50 @@ const ShareFile = ({
7272
const [selectedDate, setSelectedDate] = useState<string>("");
7373
const [dateValid, setDateValid] = useState<boolean>(true);
7474

75+
const initialDate = new Date();
76+
7577
const dateChanged = (newDate: string, isValid: boolean) => {
7678
setDateValid(isValid);
7779
if (isValid) {
7880
setSelectedDate(newDate);
7981
return;
8082
}
8183
setSelectedDate("");
84+
setShareURL("");
8285
};
8386

8487
useEffect(() => {
8588
if (dateValid) {
8689
setIsLoadingFile(true);
8790
setShareURL("");
8891

89-
const slDate = new Date(`${selectedDate}T23:59:59`);
92+
const slDate = new Date(`${selectedDate}`);
9093
const currDate = new Date();
9194

9295
const diffDate = slDate.getTime() - currDate.getTime();
9396

94-
const versID = distributedSetup ? dataObject.version_id : "null";
95-
96-
if (diffDate < 0) {
97-
setModalErrorSnackMessage({
98-
errorMessage: "Selected date must be greater than current time.",
99-
detailedError: "",
100-
});
101-
setShareURL("");
102-
setIsLoadingFile(false);
103-
104-
return;
97+
if (diffDate > 0) {
98+
const versID = distributedSetup ? dataObject.version_id : "null";
99+
100+
api
101+
.invoke(
102+
"GET",
103+
`/api/v1/buckets/${bucketName}/objects/share?prefix=${
104+
dataObject.name
105+
}&version_id=${versID || "null"}${
106+
selectedDate !== "" ? `&expires=${diffDate}ms` : ""
107+
}`
108+
)
109+
.then((res: string) => {
110+
setShareURL(res);
111+
setIsLoadingFile(false);
112+
})
113+
.catch((error: ErrorResponseHandler) => {
114+
setModalErrorSnackMessage(error);
115+
setShareURL("");
116+
setIsLoadingFile(false);
117+
});
105118
}
106-
107-
if (diffDate > 604800000) {
108-
setModalErrorSnackMessage({
109-
errorMessage: "You can share a file only for less than 7 days.",
110-
detailedError: "",
111-
});
112-
setShareURL("");
113-
setIsLoadingFile(false);
114-
115-
return;
116-
}
117-
118-
api
119-
.invoke(
120-
"GET",
121-
`/api/v1/buckets/${bucketName}/objects/share?prefix=${
122-
dataObject.name
123-
}&version_id=${versID}${
124-
selectedDate !== "" ? `&expires=${diffDate}ms` : ""
125-
}`
126-
)
127-
.then((res: string) => {
128-
setShareURL(res);
129-
setIsLoadingFile(false);
130-
})
131-
.catch((error: ErrorResponseHandler) => {
132-
setModalErrorSnackMessage(error);
133-
setShareURL("");
134-
setIsLoadingFile(false);
135-
});
136-
return;
137119
}
138120
}, [
139121
dataObject,
@@ -155,13 +137,20 @@ const ShareFile = ({
155137
}}
156138
>
157139
<Grid container className={classes.modalContent}>
140+
<Grid item xs={12} className={classes.moduleDescription}>
141+
This module generates a temporary URL with integrated access
142+
credentials for sharing objects for up to 7 days.
143+
<br />
144+
The temporary URL expires after the configured time limit.
145+
</Grid>
158146
<Grid item xs={12} className={classes.dateContainer}>
159-
<DateSelector
147+
<DaysSelector
148+
initialDate={initialDate}
160149
id="date"
161-
label="Active until"
162-
borderBottom={false}
163-
addSwitch={true}
164-
onDateChange={dateChanged}
150+
label="Active for"
151+
maxDays={7}
152+
onChange={dateChanged}
153+
entity="Link"
165154
/>
166155
</Grid>
167156
<Grid container item xs={12}>
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2021 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, useEffect, Fragment } from "react";
18+
import Grid from "@material-ui/core/Grid";
19+
import InputLabel from "@material-ui/core/InputLabel";
20+
import moment from "moment/moment";
21+
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
22+
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
23+
import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper";
24+
25+
interface IDaysSelector {
26+
classes: any;
27+
id: string;
28+
initialDate: Date;
29+
maxDays?: number;
30+
label: string;
31+
entity: string;
32+
onChange: (newDate: string, isValid: boolean) => void;
33+
}
34+
35+
const styles = (theme: Theme) =>
36+
createStyles({
37+
dateInput: {
38+
"&:not(:last-child)": {
39+
marginRight: 22,
40+
},
41+
},
42+
...fieldBasic,
43+
...tooltipHelper,
44+
labelContainer: {
45+
display: "flex",
46+
alignItems: "center",
47+
},
48+
fieldContainer: {
49+
...fieldBasic.fieldContainer,
50+
display: "flex",
51+
alignItems: "center",
52+
justifyContent: "space-between",
53+
paddingBottom: 10,
54+
marginTop: 11,
55+
marginBottom: 6,
56+
},
57+
fieldContainerBorder: {
58+
borderBottom: "#9c9c9c 1px solid",
59+
marginBottom: 20,
60+
},
61+
dateContainer: {
62+
height: 20,
63+
textAlign: "right",
64+
color: "#848484",
65+
},
66+
});
67+
68+
const calculateNewTime = (
69+
initialDate: Date,
70+
days: number,
71+
hours: number,
72+
minutes: number
73+
) => {
74+
return moment(initialDate)
75+
.add(days, "days")
76+
.add(hours, "hours")
77+
.add(minutes, "minutes");
78+
};
79+
80+
const DaysSelector = ({
81+
classes,
82+
id,
83+
initialDate,
84+
label,
85+
maxDays,
86+
entity,
87+
onChange,
88+
}: IDaysSelector) => {
89+
const [selectedDays, setSelectedDays] = useState<number>(7);
90+
const [selectedHours, setSelectedHours] = useState<number>(0);
91+
const [selectedMinutes, setSelectedMinutes] = useState<number>(0);
92+
const [validDate, setValidDate] = useState<boolean>(true);
93+
const [dateSelected, setDateSelected] = useState<moment.Moment>(moment());
94+
95+
useEffect(() => {
96+
setDateSelected(
97+
calculateNewTime(
98+
initialDate,
99+
selectedDays,
100+
selectedHours,
101+
selectedMinutes
102+
)
103+
);
104+
}, [initialDate, selectedDays, selectedHours, selectedMinutes]);
105+
106+
useEffect(() => {
107+
if (validDate) {
108+
onChange(dateSelected.format("YYYY-MM-DDTHH:mm:ss"), true);
109+
} else {
110+
onChange("0000-00-00", false);
111+
}
112+
}, [dateSelected, onChange, validDate]);
113+
114+
// Basic validation for inputs
115+
useEffect(() => {
116+
let valid = true;
117+
if (
118+
selectedDays < 0 ||
119+
(maxDays && selectedDays > maxDays) ||
120+
isNaN(selectedDays)
121+
) {
122+
valid = false;
123+
}
124+
125+
if (selectedHours < 0 || selectedHours > 23 || isNaN(selectedHours)) {
126+
valid = false;
127+
}
128+
129+
if (selectedMinutes < 0 || selectedMinutes > 59 || isNaN(selectedMinutes)) {
130+
valid = false;
131+
}
132+
133+
if (
134+
maxDays &&
135+
selectedDays === maxDays &&
136+
(selectedHours !== 0 || selectedMinutes !== 0)
137+
) {
138+
valid = false;
139+
}
140+
141+
setValidDate(valid);
142+
}, [
143+
dateSelected,
144+
maxDays,
145+
onChange,
146+
selectedDays,
147+
selectedHours,
148+
selectedMinutes,
149+
]);
150+
151+
return (
152+
<Fragment>
153+
<Grid item xs={12} className={classes.fieldContainer}>
154+
<Grid container alignItems={"center"} justifyContent={"center"}>
155+
<Grid item xs={12} className={classes.labelContainer}>
156+
<InputLabel htmlFor={id} className={classes.inputLabel}>
157+
<span>{label}</span>
158+
</InputLabel>
159+
</Grid>
160+
<Grid item xs={2}>
161+
<InputBoxWrapper
162+
id={id}
163+
type="number"
164+
min="0"
165+
max={maxDays ? maxDays.toString() : "999"}
166+
label="Days"
167+
name={id}
168+
onChange={(e) => {
169+
setSelectedDays(parseInt(e.target.value));
170+
}}
171+
value={selectedDays.toString()}
172+
extraInputProps={{
173+
style: {
174+
textAlign: "center",
175+
paddingRight: 5,
176+
},
177+
className: "removeArrows",
178+
}}
179+
/>
180+
</Grid>
181+
<Grid item xs={2}>
182+
<InputBoxWrapper
183+
id={id}
184+
type="number"
185+
min="0"
186+
max="23"
187+
label="Hours"
188+
name={id}
189+
onChange={(e) => {
190+
setSelectedHours(parseInt(e.target.value));
191+
}}
192+
value={selectedHours.toString()}
193+
extraInputProps={{
194+
style: {
195+
textAlign: "center",
196+
paddingRight: 5,
197+
},
198+
className: "removeArrows",
199+
}}
200+
/>
201+
</Grid>
202+
<Grid item xs={2}>
203+
<InputBoxWrapper
204+
id={id}
205+
type="number"
206+
min="0"
207+
max="59"
208+
label="Minutes"
209+
name={id}
210+
onChange={(e) => {
211+
setSelectedMinutes(parseInt(e.target.value));
212+
}}
213+
value={selectedMinutes.toString()}
214+
extraInputProps={{
215+
style: {
216+
textAlign: "center",
217+
paddingRight: 5,
218+
},
219+
className: "removeArrows",
220+
}}
221+
/>
222+
</Grid>
223+
</Grid>
224+
</Grid>
225+
<Grid container>
226+
<Grid item xs={12} className={classes.dateContainer}>
227+
{validDate && (
228+
<Fragment>
229+
<strong>{entity} will be available until:</strong>{" "}
230+
{dateSelected.format("MM/DD/YYYY HH:mm:ss")}
231+
</Fragment>
232+
)}
233+
</Grid>
234+
</Grid>
235+
<br />
236+
</Fragment>
237+
);
238+
};
239+
240+
export default withStyles(styles)(DaysSelector);

0 commit comments

Comments
 (0)