-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Task/wg 381 assets view image video no asset (#292)
* task/WG-381-Assets-View-Image-Video-No-Asset - Adds place and styles for panel on MapProject page * - Changes useCurrentFeature hook so that it picks up the latest query accurately - Creates asset detail component with styles - Creates asset detail placeholders for questionnaire and pointcloud - Need to add meaningful test * - Addresses requested changes. TODO: Updated geometry to be handled like angular * Requested changes 12.17 - Removes code now that selectedFeature is passed in as a prop * Adds an asset fixture * - Fixes autocomplete error in test * Add current features hook and extend selected feature hook * - Linting * - Removes unused test render * Catches weird cases where display_path does not exist but file tree uses asset.id before selectedFeature.id - See v3_PROD_Hazmapper_2024-08-07_Test map to check
- Loading branch information
1 parent
88d89fc
commit 04a764f
Showing
8 changed files
with
423 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
.root { | ||
display: flex; | ||
flex-direction: column; | ||
width: 100%; | ||
height: 100%; | ||
overflow: hidden; | ||
} | ||
|
||
.topSection { | ||
flex: 0 0 auto; | ||
padding: 10px; | ||
display: flex; | ||
text-align: center; | ||
justify-content: space-between; | ||
align-items: center; | ||
font-size: large; | ||
} | ||
|
||
.middleSection { | ||
flex: 1 1 auto; | ||
overflow: hidden; | ||
min-height: 0; | ||
display: flex; | ||
text-align: center; | ||
flex-direction: column; | ||
justify-content: center; | ||
align-items: center; | ||
gap: 10px; | ||
} | ||
.middleSection > div { | ||
flex: 0 1 auto; | ||
overflow: auto; | ||
} | ||
.assetContainer { | ||
display: flex; | ||
align-content: center; | ||
justify-content: center; | ||
} | ||
.assetContainer > * { | ||
max-width: 100%; | ||
} | ||
.bottomSection { | ||
display: block; | ||
flex: 0 0 auto; | ||
overflow-x: hidden; | ||
justify-items: flex-end; | ||
align-items: flex-end; | ||
width: 100%; | ||
} | ||
.metadataTable { | ||
flex-grow: 1; | ||
width: 100%; | ||
} | ||
.metadataTable table { | ||
width: 100%; | ||
table-layout: fixed; | ||
border-collapse: collapse; | ||
margin: 5px; | ||
padding: 5px; | ||
} | ||
.metadataTable thead, | ||
th { | ||
background: #d0d0d0; | ||
} | ||
.metadataTable tbody { | ||
display: block; | ||
max-height: 300px; | ||
overflow-y: auto; | ||
scroll-padding: 5px; | ||
} | ||
.metadataTable tr { | ||
display: table; | ||
width: 100%; | ||
table-layout: fixed; | ||
text-overflow: ellipsis; | ||
overflow: hidden; | ||
white-space: wrap; | ||
max-height: 30px; | ||
} | ||
.metadataTable td { | ||
word-wrap: break-word; | ||
word-break: break-word; | ||
white-space: normal; | ||
text-overflow: clip; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import AssetDetail from './AssetDetail'; | ||
import { mockImgFeature } from '@hazmapper/__fixtures__/featuresFixture'; | ||
|
||
jest.mock('@hazmapper/hooks', () => ({ | ||
useFeatureSelection: jest.fn(), | ||
useAppConfiguration: jest.fn().mockReturnValue({ | ||
geoapiUrl: 'https://example.com/geoapi', | ||
}), | ||
})); | ||
|
||
describe('AssetDetail', () => { | ||
const AssetModalProps = { | ||
onClose: jest.fn(), | ||
selectedFeature: mockImgFeature, | ||
isPublicView: false, | ||
}; | ||
|
||
it('renders all main components', () => { | ||
const { getByText } = render(<AssetDetail {...AssetModalProps} />); | ||
// Check for title, button, and tables | ||
expect(getByText('Photo 4.jpg')).toBeDefined(); | ||
expect(getByText('Download')).toBeDefined(); | ||
expect(getByText('Metadata')).toBeDefined(); | ||
expect(getByText('Geometry')).toBeDefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import React, { Suspense } from 'react'; | ||
import _ from 'lodash'; | ||
import { useAppConfiguration } from '@hazmapper/hooks'; | ||
import { FeatureTypeNullable, Feature, getFeatureType } from '@hazmapper/types'; | ||
import { FeatureIcon } from '@hazmapper/components/FeatureIcon'; | ||
import { Button, LoadingSpinner, SectionMessage } from '@tacc/core-components'; | ||
import styles from './AssetDetail.module.css'; | ||
|
||
type AssetModalProps = { | ||
onClose: () => void; | ||
selectedFeature: Feature; | ||
isPublicView: boolean; | ||
}; | ||
|
||
const AssetDetail: React.FC<AssetModalProps> = ({ | ||
selectedFeature, | ||
onClose, | ||
isPublicView, | ||
}) => { | ||
const config = useAppConfiguration(); | ||
const geoapiUrl = config.geoapiUrl; | ||
|
||
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'; | ||
|
||
return ( | ||
<div className={styles.root}> | ||
<div className={styles.topSection}> | ||
<FeatureIcon featureType={fileType as FeatureTypeNullable} /> | ||
{selectedFeature?.assets?.length > 0 | ||
? selectedFeature?.assets.map((asset) => | ||
// To make sure fileTree name matches title and catches null | ||
asset.display_path | ||
? asset.display_path.split('/').pop() | ||
: asset.id | ||
? asset.id | ||
: selectedFeature.id | ||
) | ||
: selectedFeature?.id} | ||
<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> | ||
)} | ||
</> | ||
)} | ||
</div> | ||
<div className={styles.bottomSection}> | ||
<div className={styles.metadataTable}> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th colSpan={2}>Metadata</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{selectedFeature?.properties && | ||
Object.keys(selectedFeature.properties).length > 0 ? ( | ||
Object.entries(selectedFeature.properties) | ||
.filter(([key]) => !key.startsWith('_hazmapper')) | ||
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) // Alphabetizes metadata | ||
.map(([propKey, propValue]) => ( | ||
<tr key={propKey}> | ||
<td>{_.startCase(propKey)}</td> | ||
<td> | ||
{propKey.startsWith('description') ? ( | ||
<code>{propValue}</code> | ||
) : ( | ||
_.trim(JSON.stringify(propValue), '"') | ||
)} | ||
</td> | ||
</tr> | ||
)) | ||
) : ( | ||
<tr> | ||
<td colSpan={2}>There are no metadata properties.</td> | ||
</tr> | ||
)} | ||
</tbody> | ||
</table> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th colSpan={2}>Geometry</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{selectedFeature?.geometry && | ||
Object.entries(selectedFeature.geometry).map( | ||
([propKey, propValue]) => | ||
propValue && | ||
propValue !== undefined && | ||
propValue.toString().trim() !== '' && | ||
propValue.toString() !== 'null' && ( | ||
<tr key={propKey}> | ||
<td>{_.trim(_.startCase(propKey.toString()), '"')}</td> | ||
<td> | ||
{' '} | ||
{Array.isArray(propValue) && propValue.length === 2 | ||
? `Latitude: ${propValue[0].toString()}, | ||
Longitude: ${propValue[1].toString()}` | ||
: _.trim(JSON.stringify(propValue), '"')} | ||
</td> | ||
</tr> | ||
) | ||
)} | ||
</tbody> | ||
</table> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AssetDetail; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './AssetDetail'; |
Oops, something went wrong.