Skip to content

Commit

Permalink
Task/WG-238 panel assets listing (#261)
Browse files Browse the repository at this point in the history
* Add initial feature file tree

* Additional work on assets list

* Add sorting

* Improve doc

* Improve styling and fix nodes of tree

* Add todo

* Refactor and add test

* Refactor into new files

* Move FeatureFileNode into types

* Use font awesome for folder icon

* Fix spelling error

* Rework importing

* Expand rows

* Set witch of panel as 230px

* Remove todos

* Ensure nodes are expanded at start

* Fix selection/hover and height of rows

* Fix expanding rows on refresh

* Add selected feature to route and fix row spacing

* Add useDelete

From #268

* Add functionality for feature deletion

* Refactor MapProject

* Fix todos

* Add isLoading to button

* Allow user to export feature geojson to file

* Add feature icon in asset listing

* Refactor types related to feature icon

* Add missing useDeleteFeature

* Add unit testing of getFeatureType

* Fix linting

* Add AssetsPanel test

* Add FeatureFileTree unit test

* Bump testing-library/react to get rid of act warnings

* Removed unused

* Add antd

* Switch to antd tree instead of react-table

* Fix accessibility-related warnings and fix unit tests

* Adjust width and overflow

* Fix height issues

* Improve navbar

* Remove deprecated typs/antd.

Types are included already with antd

* Limit getting features to a single time

* Fix scaling of panel container

* Rework tree to deal with virtual rendering issues

4px bottom butter was being added to each node
which caused an issue in calculating how much vertical
space was needed to render nodes.

* Make nav bar scrolling when y overflows

* Make contents of project view take up space below nav/control bar

* Removed unneeded config and styles

* Move hazmapper globals to hazmapper.css

* Add missing file

* Update act import

* Fix bad merge

---------

Co-authored-by: Author: sophia-massie <[email protected]>
  • Loading branch information
nathanfranklin and sophia-massie authored Nov 11, 2024
1 parent a54b319 commit 299d436
Show file tree
Hide file tree
Showing 31 changed files with 2,295 additions and 314 deletions.
1,432 changes: 1,242 additions & 190 deletions react/package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@changey/react-leaflet-markercluster": "^4.0.0-rc1",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@reduxjs/toolkit": "^1.8.4",
"@tacc/core-components": "^0.0.3-beta.0",
"@tacc/core-styles": "^2.24.1",
"@turf/turf": "^6.5.0",
"@types/geojson": "^7946.0.14",
"@types/leaflet.markercluster": "^1.5.1",
"antd": "^5.21.6",
"axios": "^1.6.2",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
Expand Down
63 changes: 54 additions & 9 deletions react/src/__fixtures__/featuresFixture.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Point } from 'geojson';
import { FeatureCollection, AssetType } from '@hazmapper/types';

export const featureCollection = {
export const featureCollection: FeatureCollection = {
type: 'FeatureCollection',
features: [
{
id: 2053334,
id: 1,
project_id: 80,
type: 'Feature',
geometry: {
Expand All @@ -18,15 +19,15 @@ export const featureCollection = {
id: 2036568,
path: '80/f7c2afa2-f184-40a7-80be-0874a7db755e.jpeg',
uuid: 'f7c2afa2-f184-40a7-80be-0874a7db755e',
asset_type: 'image',
original_path: '/nathanf/image1.JPG',
asset_type: 'image' as AssetType,
original_path: '/foo/image1.JPG',
original_name: null,
display_path: '/nathanf/image1.JPG',
display_path: '/foo/image1.JPG',
},
],
},
{
id: 2053335,
id: 2,
project_id: 80,
type: 'Feature',
geometry: {
Expand All @@ -40,10 +41,54 @@ export const featureCollection = {
id: 2036569,
path: '80/5a3e7513-2740-4c0f-944d-7b497ceff2ea.jpeg',
uuid: '5a3e7513-2740-4c0f-944d-7b497ceff2ea',
asset_type: 'image',
original_path: '/nathanf/image1.JPG',
asset_type: 'image' as AssetType,
original_path: '/foo/image2.JPG',
original_name: null,
display_path: '/foo/image2.JPG',
},
],
},
],
};

export const featureCollectionWithNestedPaths: FeatureCollection = {
type: 'FeatureCollection',
features: [
{
id: 1,
project_id: 1,
type: 'Feature',
geometry: { type: 'Point', coordinates: [0, 0] } as Point,
properties: {},
styles: {},
assets: [
{
id: 1,
display_path: 'folder1/file1.jpg',
path: '',
uuid: '',
asset_type: 'image' as AssetType,
original_path: '',
original_name: null,
},
],
},
{
id: 2,
project_id: 1,
type: 'Feature',
geometry: { type: 'Point', coordinates: [0, 0] } as Point,
properties: {},
styles: {},
assets: [
{
id: 2,
display_path: 'folder1/subfolder/file2.rq',
path: '',
uuid: '',
asset_type: 'questionnaire' as AssetType,
original_path: '',
original_name: null,
display_path: '/nathanf/image1.JPG',
},
],
},
Expand Down
23 changes: 23 additions & 0 deletions react/src/components/AssetsPanel/AssetsPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,27 @@
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
}

.topSection,
.bottomSection {
flex: 0 0 auto;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
}

.middleSection {
flex: 1 1 auto;
overflow: hidden;
min-height: 0;
display: flex;
flex-direction: column;
}

.middleSection > div {
flex: 1;
overflow: auto;
}
60 changes: 60 additions & 0 deletions react/src/components/AssetsPanel/AssetsPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { act } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import AssetsPanel from './AssetsPanel';
import { featureCollection } from '@hazmapper/__fixtures__/featuresFixture';
import { useFeatures } from '@hazmapper/hooks';

jest.mock('@hazmapper/hooks', () => ({
useFeatures: jest.fn(),
}));

// Mock FeatureFileTree component since it's a complex component and tested elswhere
jest.mock('@hazmapper/components/FeatureFileTree', () => {
return function MockFeatureFileTree() {
return <div data-testid="feature-file-tree">FeatureFileTree Component</div>;
};
});

describe('AssetsPanel', () => {
const defaultProps = {
featureCollection,
projectId: 1,
isPublic: false,
};

beforeEach(() => {
jest.clearAllMocks();

// Setup default mock implementation for useFeatures which is used for downloading geojson
(useFeatures as jest.Mock).mockReturnValue({
isLoading: false,
refetch: jest.fn(),
});
});

it('renders all main components', () => {
const { getByText } = render(<AssetsPanel {...defaultProps} />);

// Check for the presence of buttons
expect(getByText('Import from DesignSafe TODO/WG-387')).toBeDefined();
expect(getByText('Export to GeoJSON')).toBeDefined();
});

it('handles GeoJSON export correctly', async () => {
// Mock the useFeatures hook for download scenario
const mockRefetch = jest.fn();
(useFeatures as jest.Mock).mockReturnValue({
isLoading: false,
refetch: mockRefetch,
});

render(<AssetsPanel {...defaultProps} />);

// Click export button
await act(async () => {
fireEvent.click(screen.getByText('Export to GeoJSON'));
});
// Verify refetch was called
expect(mockRefetch).toHaveBeenCalled();
});
});
82 changes: 79 additions & 3 deletions react/src/components/AssetsPanel/AssetsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,95 @@
import React from 'react';
import styles from './AssetsPanel.module.css';
import FeatureFileTree from '@hazmapper/components/FeatureFileTree';
import { FeatureCollection } from '@hazmapper/types';
import { Button } from '@tacc/core-components';
import { useFeatures } from '@hazmapper/hooks';

interface DownloadFeaturesButtonProps {
projectId: number;
isPublic: boolean;
}

const DownloadFeaturesButton: React.FC<DownloadFeaturesButtonProps> = ({
projectId,
isPublic,
}) => {
const { isLoading: isDownloading, refetch: triggerDownload } = useFeatures({
projectId,
isPublic,
assetTypes: [], // Empty array to get all features
options: {
enabled: false, // Only fetch when triggered by user clicking button
cacheTime: 0,
staleTime: 0,
onSuccess: (data: FeatureCollection) => {
// Create and trigger download
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json',
});

const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `hazmapper.json`;

document.body.appendChild(link);
link.click();

document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
},
});

return (
<Button isLoading={isDownloading} onClick={() => triggerDownload()}>
Export to GeoJSON
</Button>
);
};

interface Props {
/**
* Features of map
*/
featureCollection: FeatureCollection;

/**
* Whether or not the map project is public.
*/
isPublic: boolean;

/**
* active project id
*/
projectId: number;
}

/**
* A component that displays a map project (a map and related data)
* A panel component that displays info on feature assets
*/
const AssetsPanel: React.FC<Props> = ({ isPublic }) => {
const AssetsPanel: React.FC<Props> = ({
isPublic,
featureCollection,
projectId,
}) => {
return (
<div className={styles.root}>Assets Panel TODO, isPublic: {isPublic}</div>
<div className={styles.root}>
<div className={styles.topSection}>
<Button>Import from DesignSafe TODO/WG-387</Button>
</div>
<div className={styles.middleSection}>
<FeatureFileTree
projectId={projectId}
isPublic={isPublic}
featureCollection={featureCollection}
/>
</div>
<div className={styles.bottomSection}>
<DownloadFeaturesButton projectId={projectId} isPublic={isPublic} />
</div>
</div>
);
};

Expand Down
3 changes: 1 addition & 2 deletions react/src/components/DeleteMapModal/DeleteMapModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import React, { act } from 'react';
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
import { QueryClient, QueryClientProvider } from 'react-query';
import DeleteMapModal from './DeleteMapModal';
import { Provider } from 'react-redux';
Expand Down
57 changes: 57 additions & 0 deletions react/src/components/FeatureFileTree/FeatureFileTree.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.root {
width: 100%;
overflow: visible;

:global(.ant-tree .ant-tree-treenode) {
margin-bottom: 0px !important; /*needed to ensure calculation of row hight is right */
}

/* Remove switcher space */
:global(.ant-tree-switcher) {
display: none;
}

/* Hovering over non-selected items */
:global(.ant-tree-node-content-wrapper:hover:not(.ant-tree-node-selected)) {
background-color: var(--global-color-accent--weak) !important;
}

/* Selected items (both normal and hover state) */ /*TODO*/
:global(.ant-tree-node-content-wrapper.ant-tree-node-selected) {
background-color: var(--global-color-accent--weak) !important;
}
}

.featureFileTree {
height: 100%;

:global(.ant-tree-node-content-wrapper) {
/*https://tacc-main.atlassian.net/browse/WG-390*/
font-size: var(--global-font-family--small);
font-family: var(--global-font-family--sans);
max-width: var(--hazmapper-panel-width); /* fixed width of panel*/
padding-left: 5px;
padding-right: 5px;
overflow: hidden;
}

/* Adjust indent size */
:global(.ant-tree-indent-unit) {
width: 8px;
}
}

.treeNode {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}

.fileName {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 0.25em;
}
Loading

0 comments on commit 299d436

Please sign in to comment.