Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task/WG-393-Asset-Detail-Pointclouds #296

Merged
merged 10 commits into from
Jan 9, 2025
15 changes: 15 additions & 0 deletions react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@types/leaflet.markercluster": "^1.5.1",
"antd": "^5.21.6",
"axios": "^1.6.2",
"dompurify": "^3.2.3",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1",
Expand Down
44 changes: 44 additions & 0 deletions react/src/components/AssetDetail/AssetButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import DOMPurify from 'dompurify';
import { Button } from '@tacc/core-components';
import { Feature, FeatureType } from '@hazmapper/types';
import { getFeatureType } from '@hazmapper/types';

type AssetButtonProps = {
selectedFeature: Feature;
featureSource: string;
isPublicView: boolean;
};

const AssetButton: React.FC<AssetButtonProps> = ({
selectedFeature,
featureSource,
isPublicView,
}) => {
const pointCloudURL = DOMPurify.sanitize(featureSource + '/index.html');

const featureType = getFeatureType(selectedFeature);

return (
<>
{featureType === FeatureType.Image && (
<Button /*TODO add Download*/ type="primary">Download</Button>
)}
{featureType === FeatureType.PointCloud && (
<a href={pointCloudURL} target="_blank" rel="noreferrer">
<Button type="primary">View</Button>
</a>
)}
{featureType === FeatureType.Questionnaire && (
//TODO
<Button type="primary">View</Button>
)}
{featureType.includes(selectedFeature.geometry.type) && isPublicView && (
//TODO
<Button type="primary">Add Asset from DesignSafe</Button>
)}
</>
);
};

export default AssetButton;
5 changes: 5 additions & 0 deletions react/src/components/AssetDetail/AssetDetail.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
}
.middleSection > div {
flex: 0 1 auto;
width: 100%;
overflow: auto;
}
.assetContainer {
Expand Down Expand Up @@ -81,3 +82,7 @@ th {
word-break: break-word;
white-space: normal;
}
.pointCloud {
height: 35em;
width: 100%;
}
77 changes: 24 additions & 53 deletions react/src/components/AssetDetail/AssetDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import React, { Suspense } from 'react';
import _ from 'lodash';
import AssetGeometry from './AssetGeometry';
import { useAppConfiguration } from '@hazmapper/hooks';
import { FeatureTypeNullable, Feature, getFeatureType } from '@hazmapper/types';
import AssetRenderer from './AssetRenderer';
import AssetButton from './AssetButton';
import {
FeatureTypeNullable,
Feature,
getFeatureType,
FeatureType,
} from '@hazmapper/types';
import { FeatureIcon } from '@hazmapper/components/FeatureIcon';
import { Button, LoadingSpinner, SectionMessage } from '@tacc/core-components';
import { Button, LoadingSpinner } from '@tacc/core-components';
import styles from './AssetDetail.module.css';

type AssetModalProps = {
Expand All @@ -24,42 +31,12 @@ const AssetDetail: React.FC<AssetModalProps> = ({
const featureSource: string =
geoapiUrl + '/assets/' + selectedFeature?.assets?.[0]?.path;

const fileType = getFeatureType(selectedFeature);

const AssetRenderer = React.memo(
({
type,
source,
}: {
type: string | undefined;
source: string | undefined;
}) => {
switch (type) {
case 'image':
return <img src={source} alt="Asset" loading="lazy" />;
case 'video':
return (
<video src={source} controls preload="metadata">
<track kind="captions" />
</video>
);
case 'point_cloud':
/*TODO Add pointcloud */
return <div> source={source}</div>;
case 'questionnaire':
/*TODO Add questionnaire */
return <div> source={source}</div>;
default:
return null;
}
}
);
AssetRenderer.displayName = 'AssetRenderer';
const featureType: FeatureType = getFeatureType(selectedFeature);

return (
<div className={styles.root}>
<div className={styles.topSection}>
<FeatureIcon featureType={fileType as FeatureTypeNullable} />
<FeatureIcon featureType={featureType as FeatureTypeNullable} />
{selectedFeature?.assets?.length > 0
? selectedFeature?.assets.map((asset) =>
// To make sure fileTree name matches title and catches null
Expand All @@ -73,25 +50,19 @@ const AssetDetail: React.FC<AssetModalProps> = ({
<Button type="link" iconNameAfter="close" onClick={onClose}></Button>
</div>
<div className={styles.middleSection}>
{fileType ? (
<>
<Suspense fallback={<LoadingSpinner />}>
<div className={styles.assetContainer}>
<AssetRenderer type={fileType} source={featureSource} />
</div>
</Suspense>
<Button /*TODO Download Action */>Download</Button>
</>
) : (
<>
<SectionMessage type="info">Feature has no asset.</SectionMessage>
{!isPublicView && (
<Button type="primary" /* TODO Add asset to a feature */>
Add asset from DesignSafe
</Button>
)}
</>
)}
<Suspense fallback={<LoadingSpinner />}>
<div className={styles.assetContainer}>
sophia-massie marked this conversation as resolved.
Show resolved Hide resolved
<AssetRenderer
selectedFeature={selectedFeature}
featureSource={featureSource}
/>
</div>
<AssetButton
sophia-massie marked this conversation as resolved.
Show resolved Hide resolved
selectedFeature={selectedFeature}
featureSource={featureSource}
isPublicView={isPublicView}
/>
</Suspense>
</div>
<div className={styles.bottomSection}>
<div className={styles.metadataTable}>
Expand Down
20 changes: 20 additions & 0 deletions react/src/components/AssetDetail/AssetPointCloud.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import DOMPurify from 'dompurify';
import styles from './AssetDetail.module.css';

type PointCloudProps = {
featureSource: string;
};

const AssetPointCloud: React.FC<PointCloudProps> = ({ featureSource }) => {
const sanitizedSource = DOMPurify.sanitize(featureSource + '/preview.html');

return (
<iframe
className={styles.pointCloud}
src={sanitizedSource}
title={featureSource}
></iframe>
);
};
export default AssetPointCloud;
47 changes: 47 additions & 0 deletions react/src/components/AssetDetail/AssetRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { Feature, FeatureType, getFeatureType } from '@hazmapper/types';
import { SectionMessage } from '@tacc/core-components';
import AssetPointCloud from './AssetPointCloud';

interface AssetRendererProps {
selectedFeature: Feature;
featureSource: string;
}

const AssetRenderer: React.FC<AssetRendererProps> = ({
selectedFeature,
featureSource,
}) => {
const featureType: FeatureType = getFeatureType(selectedFeature);

const isGeometry = (featureType: FeatureType): boolean => {
return featureType.includes(selectedFeature.geometry.type);
};

switch (featureType) {
case FeatureType.Image:
return <img src={featureSource} alt="Asset" loading="lazy" />;
case FeatureType.Video:
return (
<video src={featureSource} controls preload="metadata">
<track kind="captions" />
</video>
);
case FeatureType.PointCloud:
return <AssetPointCloud featureSource={featureSource} />;
case FeatureType.Questionnaire:
return <div>source={featureSource}</div>;
case FeatureType.GeometryCollection:
default:
if (isGeometry(featureType)) {
return (
<SectionMessage type="info">
This feature has no asset.
</SectionMessage>
);
}
return <SectionMessage type="warn">Unknown asset</SectionMessage>;
}
};

export default AssetRenderer;
Loading