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-428-import-asset-to-feature #323

Merged
merged 9 commits into from
Feb 14, 2025
68 changes: 46 additions & 22 deletions react/src/components/AssetDetail/AssetButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import React, { useState } from 'react';
import DOMPurify from 'dompurify';
import { Button } from '@tacc/core-components';
import { Feature, FeatureType } from '@hazmapper/types';
import { getFeatureType, IFileImportRequest } from '@hazmapper/types';
import { useImportFeatureAsset } from '@hazmapper/hooks';
import {
getFeatureType,
IFileImportRequest,
TapisFilePath,
} from '@hazmapper/types';
import { useImportFeatureAsset, useNotification } from '@hazmapper/hooks';
import FileBrowserModal from '../FileBrowserModal/FileBrowserModal';
import { IMPORTABLE_FEATURE_ASSET_TYPES } from '@hazmapper/utils/fileUtils';

type AssetButtonProps = {
selectedFeature: Feature;
Expand All @@ -19,27 +25,35 @@ const AssetButton: React.FC<AssetButtonProps> = ({
onQuestionnaireClick,
}) => {
const pointCloudURL = DOMPurify.sanitize(featureSource + '/index.html');

const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const featureType = getFeatureType(selectedFeature);
const projectId = selectedFeature.project_id;
const notification = useNotification();
const featureId = selectedFeature.id;
const {
mutate: importFeatureAsset,
isPending: isImporting,
isSuccess: isImportingSuccess,
} = useImportFeatureAsset(projectId, featureId);
const { mutate: importFeatureAsset, isPending: isImporting } =
useImportFeatureAsset(projectId, featureId);

const handleImportFeatureAsset = (importData: IFileImportRequest) => {
importFeatureAsset(importData);
};
const handleSubmit = () => {
const importData: IFileImportRequest = {
/*TODO Replace with passed in values from
FileBrowserModal. These are hardcoded to test.*/
system_id: 'project-4072868216578445806-242ac117-0001-012',
path: 'images_good/image.jpg',
};
handleImportFeatureAsset(importData);
const handleSubmit = (files: TapisFilePath[]) => {
for (const file of files) {
const importData: IFileImportRequest = {
system_id: file.system,
path: file.path,
};
importFeatureAsset(importData, {
onSuccess: () => {
setIsModalOpen(false);
notification.success({
description: `Your asset import for feature ${selectedFeature.id} was successful.`,
});
},
onError: (error) => {
setIsModalOpen(false);
notification.success({
description: `There was an error importing your asset for feature ${selectedFeature.id}. Error: ${error}`,
});
},
});
}
};

return (
Expand All @@ -61,13 +75,23 @@ const AssetButton: React.FC<AssetButtonProps> = ({
//TODO
<Button
type="primary"
onClick={handleSubmit}
onClick={() => setIsModalOpen(true)}
isLoading={isImporting}
disabled={isImportingSuccess}
disabled={isImporting}
>
Add Asset from DesignSafe
</Button>
)}
{isModalOpen && (
<FileBrowserModal
isOpen={isModalOpen}
toggle={() => setIsModalOpen(false)}
onImported={handleSubmit}
allowedFileExtensions={IMPORTABLE_FEATURE_ASSET_TYPES}
isSingleSelectMode={true}
singleSelectErrorMessage="Adding multiple assets to a feature is not supported."
/>
)}
</>
);
};
Expand Down
30 changes: 18 additions & 12 deletions react/src/components/AssetDetail/AssetDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,24 @@ const AssetDetail: React.FC<AssetDetailProps> = ({
</div>
<div className={styles.middleSection}>
<Suspense fallback={<LoadingSpinner />}>
<div className={styles.assetContainer}>
<AssetRenderer
selectedFeature={selectedFeature}
featureSource={featureSource}
/>
</div>
<AssetButton
selectedFeature={selectedFeature}
featureSource={featureSource}
isPublicView={isPublicView}
onQuestionnaireClick={onQuestionnaireClick}
/>
{featureType == FeatureType.Collection ? (
<div>Collections of assets not supported</div>
) : (
<>
<div className={styles.assetContainer}>
<AssetRenderer
selectedFeature={selectedFeature}
featureSource={featureSource}
/>
</div>
<AssetButton
selectedFeature={selectedFeature}
featureSource={featureSource}
isPublicView={isPublicView}
onQuestionnaireClick={onQuestionnaireClick}
/>
</>
)}
</Suspense>
</div>
{featureType !== FeatureType.Questionnaire && (
Expand Down
34 changes: 28 additions & 6 deletions react/src/components/FileBrowserModal/FileBrowserModal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, { useState } from 'react';
import { Modal, Button, Layout, Typography } from 'antd';
import { Modal, Button, Layout, Typography, Flex } from 'antd';
import { FileListing } from '../Files';
import { File, TapisFilePath } from '@hazmapper/types';
import { convertFilesToTapisPaths } from '@hazmapper/utils/fileUtils';
import { SectionMessage } from '@tacc/core-components';

type FileBrowserModalProps = {
isOpen: boolean;
toggle: () => void;
onImported?: (files: TapisFilePath[]) => void;
allowedFileExtensions: string[];
singleSelectErrorMessage?: string;
isSingleSelectMode?: boolean;
};

const { Content, Header } = Layout;
Expand All @@ -19,9 +22,10 @@ const FileBrowserModal = ({
toggle: parentToggle,
onImported,
allowedFileExtensions = [],
isSingleSelectMode = false,
singleSelectErrorMessage = '',
}: FileBrowserModalProps) => {
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);

const handleClose = () => {
parentToggle();
};
Expand All @@ -40,12 +44,27 @@ const FileBrowserModal = ({

return (
<Modal
title={<Header style={{ fontSize: '2rem' }}>Select Files</Header>}
title={
<Header style={{ fontSize: '2rem' }}>
Select File{!isSingleSelectMode && 's'}
</Header>
}
open={isOpen}
onCancel={handleClose}
footer={[
<Text key="fileCount" type="secondary" style={{ marginRight: 16 }}>
{selectedFiles.length > 0 && `${selectedFiles.length} files selected`}
<Flex vertical justify="space-evenly">
{isSingleSelectMode && selectedFiles.length > 1 && (
<SectionMessage type="error">
{singleSelectErrorMessage}
</SectionMessage>
)}
<Flex justify="space-between">
{isSingleSelectMode && <div>You may only import one file.</div>}
{selectedFiles.length > 0 &&
`${selectedFiles.length} files selected`}
</Flex>
</Flex>
</Text>,
<Button key="closeModalButton" onClick={handleClose}>
Cancel
Expand All @@ -55,7 +74,10 @@ const FileBrowserModal = ({
htmlType="submit"
type="primary"
onClick={handleImport}
disabled={selectedFiles.length === 0} // Disable if no files are selected
disabled={
selectedFiles.length === 0 ||
(isSingleSelectMode && selectedFiles.length > 1)
} // Disable if no files are selected. If single select mode, disable if multiple files are selected
>
Import
</Button>,
Expand All @@ -71,7 +93,7 @@ const FileBrowserModal = ({
<br />
<Text type="secondary">
Note: Only files are selectable, not folders. Double-click on a folder
to navigate into it.
to navigate into it.{' '}
</Text>
<div style={{ marginTop: '1rem' }}>
<FileListing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ class Question {
section.questions.push(this);
// add to question map
this.parent_template.question_map[this.template_question_num] = this;
his.parent_template.question_id_map[this.id] = this;
this.parent_template.question_id_map[this.id] = this;
}
}

Expand Down
12 changes: 12 additions & 0 deletions react/src/hooks/features/useImportFeatureAsset.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { usePost } from '../../requests';
import { useQueryClient } from '@tanstack/react-query';
import { ApiService, Feature, IFileImportRequest } from '@hazmapper/types';
import { KEY_USE_FEATURES } from './useFeatures';

export const useImportFeatureAsset = (projectId: number, featureId: number) => {
const queryClient = useQueryClient();
const endpoint = `/projects/${projectId}/features/${featureId}/assets/`;
return usePost<IFileImportRequest, Feature>({
endpoint,
apiService: ApiService.Geoapi,
options: {
onSuccess: () => {
// Invalidate the main features query to get updated feature list
// This isn't done in a celery task, so onSuccess works to invalidate
queryClient.invalidateQueries({
queryKey: [KEY_USE_FEATURES],
});
},
},
});
};
Loading