Skip to content

Commit 6d81a1b

Browse files
authored
add support for preview based on content type (#2930)
1 parent b2fe478 commit 6d81a1b

File tree

5 files changed

+210
-82
lines changed

5 files changed

+210
-82
lines changed

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import {
6060
} from "../../../../Common/FormComponents/common/styleLibrary";
6161
import { Badge } from "@mui/material";
6262
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
63-
import { extensionPreview } from "../utils";
63+
import { AllowedPreviews, previewObjectType } from "../utils";
6464
import { ErrorResponseHandler } from "../../../../../../common/types";
6565

6666
import { AppState, useAppDispatch } from "../../../../../../store";
@@ -301,6 +301,9 @@ const ListObjects = () => {
301301
const [canPreviewFile, setCanPreviewFile] = useState<boolean>(false);
302302
const [quota, setQuota] = useState<BucketQuota | null>(null);
303303

304+
const [metaData, setMetaData] = useState<any>(null);
305+
const [isMetaDataLoaded, setIsMetaDataLoaded] = useState(false);
306+
304307
const isVersioningApplied = isVersionedMode(versioningConfig.status);
305308
const bucketName = params.bucketName || "";
306309

@@ -365,6 +368,37 @@ const ListObjects = () => {
365368
(state: AppState) => state.objectBrowser.selectedObjects
366369
);
367370

371+
const fetchMetadata = useCallback(() => {
372+
const objectName = selectedObjects[0];
373+
374+
if (!isMetaDataLoaded) {
375+
const encodedPath = encodeURLString(objectName);
376+
api.buckets
377+
.getObjectMetadata(bucketName, {
378+
prefix: encodedPath,
379+
})
380+
.then((res) => {
381+
let metadata = get(res.data, "objectMetadata", {});
382+
setIsMetaDataLoaded(true);
383+
setMetaData(metadata);
384+
})
385+
.catch((err) => {
386+
console.error(
387+
"Error Getting Metadata Status: ",
388+
err,
389+
err?.detailedError
390+
);
391+
setIsMetaDataLoaded(true);
392+
});
393+
}
394+
}, [bucketName, selectedObjects, isMetaDataLoaded]);
395+
396+
useEffect(() => {
397+
if (bucketName && selectedObjects.length === 1) {
398+
fetchMetadata();
399+
}
400+
}, [bucketName, selectedObjects, fetchMetadata]);
401+
368402
useEffect(() => {
369403
dispatch(setSearchObjects(""));
370404
dispatch(setLoadingObjects(true));
@@ -392,8 +426,9 @@ const ListObjects = () => {
392426
useEffect(() => {
393427
if (selectedObjects.length === 1) {
394428
const objectName = selectedObjects[0];
429+
let objectType: AllowedPreviews = previewObjectType(metaData, objectName);
395430

396-
if (extensionPreview(objectName) !== "none" && canDownload) {
431+
if (objectType !== "none" && canDownload) {
397432
setCanPreviewFile(true);
398433
} else {
399434
setCanPreviewFile(false);
@@ -408,7 +443,7 @@ const ListObjects = () => {
408443
setCanShareFile(false);
409444
setCanPreviewFile(false);
410445
}
411-
}, [selectedObjects, canDownload]);
446+
}, [selectedObjects, canDownload, metaData]);
412447

413448
useEffect(() => {
414449
if (!quota && !anonymousMode) {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
spacingUtils,
4545
textStyleUtils,
4646
} from "../../../../Common/FormComponents/common/styleLibrary";
47-
import { extensionPreview } from "../utils";
47+
import { AllowedPreviews, previewObjectType } from "../utils";
4848

4949
import {
5050
decodeURLString,
@@ -395,6 +395,8 @@ const ObjectDetailPanel = ({
395395
[IAM_SCOPES.S3_DELETE_OBJECT]
396396
);
397397

398+
let objectType: AllowedPreviews = previewObjectType(metaData, currentItem);
399+
398400
const multiActionButtons = [
399401
{
400402
action: () => {
@@ -431,8 +433,7 @@ const ObjectDetailPanel = ({
431433
label: "Preview",
432434
disabled:
433435
!!actualInfo.is_delete_marker ||
434-
extensionPreview(currentItem) === "none" ||
435-
!canGetObject,
436+
(objectType === "none" && !canGetObject),
436437
icon: <PreviewIcon />,
437438
tooltip: canGetObject
438439
? "Preview this File"

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

Lines changed: 118 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17-
import React, { Fragment, useState } from "react";
17+
import React, { Fragment, useCallback, useEffect, useState } from "react";
1818
import createStyles from "@mui/styles/createStyles";
1919
import withStyles from "@mui/styles/withStyles";
2020
import { Grid, LinearProgress } from "@mui/material";
2121
import { BucketObjectItem } from "../ListObjects/types";
22-
import { extensionPreview } from "../utils";
22+
import { AllowedPreviews, previewObjectType } from "../utils";
2323
import { encodeURLString } from "../../../../../../common/utils";
2424
import clsx from "clsx";
25+
import WarningMessage from "../../../../Common/WarningMessage/WarningMessage";
26+
import { api } from "../../../../../../api";
27+
import get from "lodash/get";
2528

2629
const styles = () =>
2730
createStyles({
@@ -72,6 +75,40 @@ const PreviewFile = ({
7275
}: IPreviewFileProps) => {
7376
const [loading, setLoading] = useState<boolean>(true);
7477

78+
const [metaData, setMetaData] = useState<any>(null);
79+
const [isMetaDataLoaded, setIsMetaDataLoaded] = useState(false);
80+
81+
const objectName = object?.name || "";
82+
83+
const fetchMetadata = useCallback(() => {
84+
if (!isMetaDataLoaded) {
85+
const encodedPath = encodeURLString(objectName);
86+
api.buckets
87+
.getObjectMetadata(bucketName, {
88+
prefix: encodedPath,
89+
})
90+
.then((res) => {
91+
let metadata = get(res.data, "objectMetadata", {});
92+
setIsMetaDataLoaded(true);
93+
setMetaData(metadata);
94+
})
95+
.catch((err) => {
96+
console.error(
97+
"Error Getting Metadata Status: ",
98+
err,
99+
err?.detailedError
100+
);
101+
setIsMetaDataLoaded(true);
102+
});
103+
}
104+
}, [bucketName, objectName, isMetaDataLoaded]);
105+
106+
useEffect(() => {
107+
if (bucketName && objectName) {
108+
fetchMetadata();
109+
}
110+
}, [bucketName, objectName, fetchMetadata]);
111+
75112
let path = "";
76113

77114
if (object) {
@@ -83,87 +120,99 @@ const PreviewFile = ({
83120
}
84121
}
85122

86-
const objectType = extensionPreview(object?.name || "");
123+
let objectType: AllowedPreviews = previewObjectType(metaData, objectName);
87124

88125
const iframeLoaded = () => {
89126
setLoading(false);
90127
};
91128

92129
return (
93130
<Fragment>
94-
{loading && (
131+
{objectType !== "none" && loading && (
95132
<Grid item xs={12}>
96133
<LinearProgress />
97134
</Grid>
98135
)}
99-
<div style={{ textAlign: "center" }}>
100-
{objectType === "video" && (
101-
<video
102-
style={{
103-
width: "auto",
104-
height: "auto",
105-
maxWidth: "calc(100vw - 100px)",
106-
maxHeight: "calc(100vh - 200px)",
107-
}}
108-
autoPlay={true}
109-
controls={true}
110-
muted={false}
111-
playsInline={true}
112-
onPlay={iframeLoaded}
113-
>
114-
<source src={path} type="video/mp4" />
115-
</video>
116-
)}
117-
{objectType === "audio" && (
118-
<audio
119-
style={{
120-
width: "100%",
121-
height: "auto",
122-
}}
123-
autoPlay={true}
124-
controls={true}
125-
muted={false}
126-
playsInline={true}
127-
onPlay={iframeLoaded}
128-
>
129-
<source src={path} type="audio/mpeg" />
130-
</audio>
131-
)}
132-
{objectType === "image" && (
133-
<img
134-
style={{
135-
width: "auto",
136-
height: "auto",
137-
maxWidth: "100vw",
138-
maxHeight: "100vh",
139-
}}
140-
src={path}
141-
alt={"preview"}
142-
onLoad={iframeLoaded}
143-
/>
144-
)}
145-
{objectType !== "video" &&
146-
objectType !== "audio" &&
147-
objectType !== "image" && (
148-
<div
149-
className={clsx(classes.iframeBase, {
150-
[classes.iframeHidden]: loading,
151-
})}
136+
{isMetaDataLoaded ? (
137+
<div style={{ textAlign: "center" }}>
138+
{objectType === "video" && (
139+
<video
140+
style={{
141+
width: "auto",
142+
height: "auto",
143+
maxWidth: "calc(100vw - 100px)",
144+
maxHeight: "calc(100vh - 200px)",
145+
}}
146+
autoPlay={true}
147+
controls={true}
148+
muted={false}
149+
playsInline={true}
150+
onPlay={iframeLoaded}
152151
>
153-
<iframe
154-
src={path}
155-
title="File Preview"
156-
allowTransparency
157-
className={`${classes.iframeContainer} ${
158-
isFullscreen ? "fullHeight" : objectType
159-
}`}
160-
onLoad={iframeLoaded}
161-
>
162-
File couldn't be loaded. Please try Download instead
163-
</iframe>
152+
<source src={path} type="video/mp4" />
153+
</video>
154+
)}
155+
{objectType === "audio" && (
156+
<audio
157+
style={{
158+
width: "100%",
159+
height: "auto",
160+
}}
161+
autoPlay={true}
162+
controls={true}
163+
muted={false}
164+
playsInline={true}
165+
onPlay={iframeLoaded}
166+
>
167+
<source src={path} type="audio/mpeg" />
168+
</audio>
169+
)}
170+
{objectType === "image" && (
171+
<img
172+
style={{
173+
width: "auto",
174+
height: "auto",
175+
maxWidth: "100vw",
176+
maxHeight: "100vh",
177+
}}
178+
src={path}
179+
alt={"preview"}
180+
onLoad={iframeLoaded}
181+
/>
182+
)}
183+
{objectType === "none" && (
184+
<div>
185+
<WarningMessage
186+
label=" File couldn't be previewed using file extension or mime type. Please
187+
try Download instead"
188+
title="Preview unavailable "
189+
/>
164190
</div>
165191
)}
166-
</div>
192+
{objectType !== "none" &&
193+
objectType !== "video" &&
194+
objectType !== "audio" &&
195+
objectType !== "image" && (
196+
<div
197+
className={clsx(classes.iframeBase, {
198+
[classes.iframeHidden]: loading,
199+
})}
200+
>
201+
<iframe
202+
src={path}
203+
title="File Preview"
204+
allowTransparency
205+
className={`${classes.iframeContainer} ${
206+
isFullscreen ? "fullHeight" : objectType
207+
}`}
208+
onLoad={iframeLoaded}
209+
>
210+
File couldn't be loaded. Please try Download instead
211+
</iframe>
212+
</div>
213+
)}
214+
</div>
215+
) : null}
167216
</Fragment>
168217
);
169218
};

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,30 @@ class DownloadHelper {
200200
}
201201
}
202202

203+
export type AllowedPreviews = "image" | "text" | "audio" | "video" | "none";
204+
export const contentTypePreview = (contentType: string): AllowedPreviews => {
205+
if (contentType) {
206+
const mimeObjectType = (contentType || "").toLowerCase();
207+
208+
if (mimeObjectType.includes("image")) {
209+
return "image";
210+
}
211+
if (mimeObjectType.includes("text")) {
212+
return "text";
213+
}
214+
if (mimeObjectType.includes("audio")) {
215+
return "audio";
216+
}
217+
if (mimeObjectType.includes("video")) {
218+
return "video";
219+
}
220+
}
221+
222+
return "none";
223+
};
224+
203225
// Review file extension by name & returns the type of preview browser that can be used
204-
export const extensionPreview = (
205-
fileName: string
206-
): "image" | "text" | "audio" | "video" | "none" => {
226+
export const extensionPreview = (fileName: string): AllowedPreviews => {
207227
const imageExtensions = [
208228
"jif",
209229
"jfif",
@@ -262,6 +282,30 @@ export const extensionPreview = (
262282
return "none";
263283
};
264284

285+
export const previewObjectType = (
286+
metaData: Record<any, any>,
287+
objectName: string
288+
) => {
289+
const metaContentType = (
290+
(metaData && metaData["Content-Type"]) ||
291+
""
292+
).toString();
293+
294+
const extensionType = extensionPreview(objectName || "");
295+
const contentType = contentTypePreview(metaContentType);
296+
297+
let objectType: AllowedPreviews = extensionType;
298+
299+
if (extensionType === contentType) {
300+
objectType = extensionType;
301+
} else if (extensionType === "none" && contentType !== "none") {
302+
objectType = contentType;
303+
} else if (contentType === "none" && extensionType !== "none") {
304+
objectType = extensionType;
305+
}
306+
307+
return objectType;
308+
};
265309
export const sortListObjects = (fieldSort: string) => {
266310
switch (fieldSort) {
267311
case "name":

0 commit comments

Comments
 (0)