From 1d8c5baaa01d60e2eb7e9a671e254294a51067ad Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 28 Sep 2020 11:55:54 +0530 Subject: [PATCH 01/22] use assetversion name in ui --- client/src/context/utils.ts | 7 +- .../components/Uploads/UploadCompleteList.tsx | 2 +- client/src/types/graphql.tsx | 2238 ++++++++--------- .../queries/asset/getUploadedAssetVersion.ts | 2 +- 4 files changed, 1124 insertions(+), 1125 deletions(-) diff --git a/client/src/context/utils.ts b/client/src/context/utils.ts index f1630d35e..7deb4de18 100644 --- a/client/src/context/utils.ts +++ b/client/src/context/utils.ts @@ -1,4 +1,4 @@ -import { Item, Project, SubjectUnitIdentifier, AssetVersion, Asset, Vocabulary } from '../types/graphql'; +import { Item, Project, SubjectUnitIdentifier, AssetVersion, Vocabulary } from '../types/graphql'; import { StateSubject, StateItem, StateProject, IngestionFile, FileUploadStatus, FileId } from './ingestion'; export function parseFileId(id: FileId): number { @@ -42,9 +42,8 @@ export function parseProjectToState(project: Project, selected: boolean): StateP }; } -export function parseAssetVersionToState(assetVersion: AssetVersion, asset: Asset, vocabulary: Vocabulary): IngestionFile { - const { idAssetVersion, StorageSize } = assetVersion; - const { FileName } = asset; +export function parseAssetVersionToState(assetVersion: AssetVersion, vocabulary: Vocabulary): IngestionFile { + const { idAssetVersion, StorageSize, FileName } = assetVersion; const { idVocabulary } = vocabulary; const id = String(idAssetVersion); diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index 6234bbdc6..aede4889f 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -68,7 +68,7 @@ function UploadListComplete(): React.ReactElement { if (fileIds.includes(id)) { return uploads.files.find(file => file.id === id); } - return parseAssetVersionToState(assetVersion, assetVersion.Asset, assetVersion.Asset.VAssetType); + return parseAssetVersionToState(assetVersion, assetVersion.Asset.VAssetType); }); const pendingFiles: IngestionFile[] = uploads.files.filter(({ status }) => status !== FileUploadStatus.COMPLETE); diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index e7692daaf..630dd210e 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1829,11 +1829,11 @@ export type GetUploadedAssetVersionQuery = ( & { AssetVersion: Array<( { __typename?: 'AssetVersion' } - & Pick + & Pick & { Asset?: Maybe<( { __typename?: 'Asset' } - & Pick + & Pick & { VAssetType?: Maybe<( { __typename?: 'Vocabulary' } @@ -2342,31 +2342,31 @@ export type GetWorkflowQuery = ( export const DiscardUploadedAssetVersionsDocument = gql` - mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { - discardUploadedAssetVersions(input: $input) { - success - } -} - `; + mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { + discardUploadedAssetVersions(input: $input) { + success + } + } + `; export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction; /** - * __useDiscardUploadedAssetVersionsMutation__ - * - * To run a mutation, you first call `useDiscardUploadedAssetVersionsMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useDiscardUploadedAssetVersionsMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [discardUploadedAssetVersionsMutation, { data, loading, error }] = useDiscardUploadedAssetVersionsMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useDiscardUploadedAssetVersionsMutation__ +* +* To run a mutation, you first call `useDiscardUploadedAssetVersionsMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useDiscardUploadedAssetVersionsMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [discardUploadedAssetVersionsMutation, { data, loading, error }] = useDiscardUploadedAssetVersionsMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useDiscardUploadedAssetVersionsMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(DiscardUploadedAssetVersionsDocument, baseOptions); } @@ -2374,34 +2374,34 @@ export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; export type DiscardUploadedAssetVersionsMutationOptions = Apollo.BaseMutationOptions; export const UploadAssetDocument = gql` - mutation uploadAsset($file: Upload!, $type: Int!) { - uploadAsset(file: $file, type: $type) { - status - idAssetVersions - error - } -} - `; + mutation uploadAsset($file: Upload!, $type: Int!) { + uploadAsset(file: $file, type: $type) { + status + idAssetVersions + error + } + } + `; export type UploadAssetMutationFn = Apollo.MutationFunction; /** - * __useUploadAssetMutation__ - * - * To run a mutation, you first call `useUploadAssetMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUploadAssetMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [uploadAssetMutation, { data, loading, error }] = useUploadAssetMutation({ - * variables: { - * file: // value for 'file' - * type: // value for 'type' - * }, - * }); - */ +* __useUploadAssetMutation__ +* +* To run a mutation, you first call `useUploadAssetMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useUploadAssetMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [uploadAssetMutation, { data, loading, error }] = useUploadAssetMutation({ + * variables: { + * file: // value for 'file' + * type: // value for 'type' + * }, + * }); + */ export function useUploadAssetMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(UploadAssetDocument, baseOptions); } @@ -2409,33 +2409,33 @@ export type UploadAssetMutationHookResult = ReturnType; export type UploadAssetMutationOptions = Apollo.BaseMutationOptions; export const CreateCaptureDataDocument = gql` - mutation createCaptureData($input: CreateCaptureDataInput!) { - createCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } -} - `; + mutation createCaptureData($input: CreateCaptureDataInput!) { + createCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } + } + `; export type CreateCaptureDataMutationFn = Apollo.MutationFunction; /** - * __useCreateCaptureDataMutation__ - * - * To run a mutation, you first call `useCreateCaptureDataMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateCaptureDataMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createCaptureDataMutation, { data, loading, error }] = useCreateCaptureDataMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateCaptureDataMutation__ +* +* To run a mutation, you first call `useCreateCaptureDataMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateCaptureDataMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createCaptureDataMutation, { data, loading, error }] = useCreateCaptureDataMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateCaptureDataMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateCaptureDataDocument, baseOptions); } @@ -2443,33 +2443,33 @@ export type CreateCaptureDataMutationHookResult = ReturnType; export type CreateCaptureDataMutationOptions = Apollo.BaseMutationOptions; export const CreateCaptureDataPhotoDocument = gql` - mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { - createCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } -} - `; + mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { + createCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } + } + `; export type CreateCaptureDataPhotoMutationFn = Apollo.MutationFunction; /** - * __useCreateCaptureDataPhotoMutation__ - * - * To run a mutation, you first call `useCreateCaptureDataPhotoMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateCaptureDataPhotoMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createCaptureDataPhotoMutation, { data, loading, error }] = useCreateCaptureDataPhotoMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateCaptureDataPhotoMutation__ +* +* To run a mutation, you first call `useCreateCaptureDataPhotoMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateCaptureDataPhotoMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createCaptureDataPhotoMutation, { data, loading, error }] = useCreateCaptureDataPhotoMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateCaptureDataPhotoMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateCaptureDataPhotoDocument, baseOptions); } @@ -2477,31 +2477,31 @@ export type CreateCaptureDataPhotoMutationHookResult = ReturnType; export type CreateCaptureDataPhotoMutationOptions = Apollo.BaseMutationOptions; export const IngestDataDocument = gql` - mutation ingestData($input: IngestDataInput!) { - ingestData(input: $input) { - success - } -} - `; + mutation ingestData($input: IngestDataInput!) { + ingestData(input: $input) { + success + } + } + `; export type IngestDataMutationFn = Apollo.MutationFunction; /** - * __useIngestDataMutation__ - * - * To run a mutation, you first call `useIngestDataMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useIngestDataMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [ingestDataMutation, { data, loading, error }] = useIngestDataMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useIngestDataMutation__ +* +* To run a mutation, you first call `useIngestDataMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useIngestDataMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [ingestDataMutation, { data, loading, error }] = useIngestDataMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useIngestDataMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(IngestDataDocument, baseOptions); } @@ -2509,33 +2509,33 @@ export type IngestDataMutationHookResult = ReturnType; export type IngestDataMutationOptions = Apollo.BaseMutationOptions; export const CreateModelDocument = gql` - mutation createModel($input: CreateModelInput!) { - createModel(input: $input) { - Model { - idModel - } - } -} - `; + mutation createModel($input: CreateModelInput!) { + createModel(input: $input) { + Model { + idModel + } + } + } + `; export type CreateModelMutationFn = Apollo.MutationFunction; /** - * __useCreateModelMutation__ - * - * To run a mutation, you first call `useCreateModelMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateModelMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createModelMutation, { data, loading, error }] = useCreateModelMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateModelMutation__ +* +* To run a mutation, you first call `useCreateModelMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateModelMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createModelMutation, { data, loading, error }] = useCreateModelMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateModelMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateModelDocument, baseOptions); } @@ -2543,33 +2543,33 @@ export type CreateModelMutationHookResult = ReturnType; export type CreateModelMutationOptions = Apollo.BaseMutationOptions; export const CreateSceneDocument = gql` - mutation createScene($input: CreateSceneInput!) { - createScene(input: $input) { - Scene { - idScene - } - } -} - `; + mutation createScene($input: CreateSceneInput!) { + createScene(input: $input) { + Scene { + idScene + } + } + } + `; export type CreateSceneMutationFn = Apollo.MutationFunction; /** - * __useCreateSceneMutation__ - * - * To run a mutation, you first call `useCreateSceneMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateSceneMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createSceneMutation, { data, loading, error }] = useCreateSceneMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateSceneMutation__ +* +* To run a mutation, you first call `useCreateSceneMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateSceneMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createSceneMutation, { data, loading, error }] = useCreateSceneMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateSceneMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateSceneDocument, baseOptions); } @@ -2577,33 +2577,33 @@ export type CreateSceneMutationHookResult = ReturnType; export type CreateSceneMutationOptions = Apollo.BaseMutationOptions; export const CreateItemDocument = gql` - mutation createItem($input: CreateItemInput!) { - createItem(input: $input) { - Item { - idItem - } - } -} - `; + mutation createItem($input: CreateItemInput!) { + createItem(input: $input) { + Item { + idItem + } + } + } + `; export type CreateItemMutationFn = Apollo.MutationFunction; /** - * __useCreateItemMutation__ - * - * To run a mutation, you first call `useCreateItemMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateItemMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createItemMutation, { data, loading, error }] = useCreateItemMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateItemMutation__ +* +* To run a mutation, you first call `useCreateItemMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateItemMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createItemMutation, { data, loading, error }] = useCreateItemMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateItemMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateItemDocument, baseOptions); } @@ -2611,33 +2611,33 @@ export type CreateItemMutationHookResult = ReturnType; export type CreateItemMutationOptions = Apollo.BaseMutationOptions; export const CreateProjectDocument = gql` - mutation createProject($input: CreateProjectInput!) { - createProject(input: $input) { - Project { - idProject - } - } -} - `; + mutation createProject($input: CreateProjectInput!) { + createProject(input: $input) { + Project { + idProject + } + } + } + `; export type CreateProjectMutationFn = Apollo.MutationFunction; /** - * __useCreateProjectMutation__ - * - * To run a mutation, you first call `useCreateProjectMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateProjectMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateProjectMutation__ +* +* To run a mutation, you first call `useCreateProjectMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateProjectMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateProjectMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateProjectDocument, baseOptions); } @@ -2645,33 +2645,33 @@ export type CreateProjectMutationHookResult = ReturnType; export type CreateProjectMutationOptions = Apollo.BaseMutationOptions; export const CreateSubjectDocument = gql` - mutation createSubject($input: CreateSubjectInput!) { - createSubject(input: $input) { - Subject { - idSubject - } - } -} - `; + mutation createSubject($input: CreateSubjectInput!) { + createSubject(input: $input) { + Subject { + idSubject + } + } + } + `; export type CreateSubjectMutationFn = Apollo.MutationFunction; /** - * __useCreateSubjectMutation__ - * - * To run a mutation, you first call `useCreateSubjectMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateSubjectMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createSubjectMutation, { data, loading, error }] = useCreateSubjectMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateSubjectMutation__ +* +* To run a mutation, you first call `useCreateSubjectMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateSubjectMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createSubjectMutation, { data, loading, error }] = useCreateSubjectMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateSubjectMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateSubjectDocument, baseOptions); } @@ -2679,33 +2679,33 @@ export type CreateSubjectMutationHookResult = ReturnType; export type CreateSubjectMutationOptions = Apollo.BaseMutationOptions; export const CreateUnitDocument = gql` - mutation createUnit($input: CreateUnitInput!) { - createUnit(input: $input) { - Unit { - idUnit - } - } -} - `; + mutation createUnit($input: CreateUnitInput!) { + createUnit(input: $input) { + Unit { + idUnit + } + } + } + `; export type CreateUnitMutationFn = Apollo.MutationFunction; /** - * __useCreateUnitMutation__ - * - * To run a mutation, you first call `useCreateUnitMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateUnitMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createUnitMutation, { data, loading, error }] = useCreateUnitMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateUnitMutation__ +* +* To run a mutation, you first call `useCreateUnitMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateUnitMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createUnitMutation, { data, loading, error }] = useCreateUnitMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateUnitMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateUnitDocument, baseOptions); } @@ -2713,36 +2713,36 @@ export type CreateUnitMutationHookResult = ReturnType; export type CreateUnitMutationOptions = Apollo.BaseMutationOptions; export const CreateUserDocument = gql` - mutation createUser($input: CreateUserInput!) { - createUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } -} - `; + mutation createUser($input: CreateUserInput!) { + createUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } + } + `; export type CreateUserMutationFn = Apollo.MutationFunction; /** - * __useCreateUserMutation__ - * - * To run a mutation, you first call `useCreateUserMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateUserMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createUserMutation, { data, loading, error }] = useCreateUserMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateUserMutation__ +* +* To run a mutation, you first call `useCreateUserMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateUserMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createUserMutation, { data, loading, error }] = useCreateUserMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateUserMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateUserDocument, baseOptions); } @@ -2750,33 +2750,33 @@ export type CreateUserMutationHookResult = ReturnType; export type CreateUserMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularyDocument = gql` - mutation createVocabulary($input: CreateVocabularyInput!) { - createVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } -} - `; + mutation createVocabulary($input: CreateVocabularyInput!) { + createVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } + } + `; export type CreateVocabularyMutationFn = Apollo.MutationFunction; /** - * __useCreateVocabularyMutation__ - * - * To run a mutation, you first call `useCreateVocabularyMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateVocabularyMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createVocabularyMutation, { data, loading, error }] = useCreateVocabularyMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateVocabularyMutation__ +* +* To run a mutation, you first call `useCreateVocabularyMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateVocabularyMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createVocabularyMutation, { data, loading, error }] = useCreateVocabularyMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateVocabularyMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateVocabularyDocument, baseOptions); } @@ -2784,33 +2784,33 @@ export type CreateVocabularyMutationHookResult = ReturnType; export type CreateVocabularyMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularySetDocument = gql` - mutation createVocabularySet($input: CreateVocabularySetInput!) { - createVocabularySet(input: $input) { - VocabularySet { - idVocabularySet - } - } -} - `; + mutation createVocabularySet($input: CreateVocabularySetInput!) { + createVocabularySet(input: $input) { + VocabularySet { + idVocabularySet + } + } + } + `; export type CreateVocabularySetMutationFn = Apollo.MutationFunction; /** - * __useCreateVocabularySetMutation__ - * - * To run a mutation, you first call `useCreateVocabularySetMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateVocabularySetMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createVocabularySetMutation, { data, loading, error }] = useCreateVocabularySetMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useCreateVocabularySetMutation__ +* +* To run a mutation, you first call `useCreateVocabularySetMutation` within a React component and pass it any options that fit your needs. +* When your component renders, `useCreateVocabularySetMutation` returns a tuple that includes: +* - A mutate function that you can call at any time to execute the mutation +* - An object with fields that represent the current status of the mutation's execution +* +* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; +* +* @example +* const [createVocabularySetMutation, { data, loading, error }] = useCreateVocabularySetMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateVocabularySetMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateVocabularySetDocument, baseOptions); } @@ -2818,31 +2818,31 @@ export type CreateVocabularySetMutationHookResult = ReturnType; export type CreateVocabularySetMutationOptions = Apollo.BaseMutationOptions; export const GetAccessPolicyDocument = gql` - query getAccessPolicy($input: GetAccessPolicyInput!) { - getAccessPolicy(input: $input) { - AccessPolicy { - idAccessPolicy - } - } -} - `; + query getAccessPolicy($input: GetAccessPolicyInput!) { + getAccessPolicy(input: $input) { + AccessPolicy { + idAccessPolicy + } + } + } + `; /** - * __useGetAccessPolicyQuery__ - * - * To run a query within a React component, call `useGetAccessPolicyQuery` and pass it any options that fit your needs. - * When your component renders, `useGetAccessPolicyQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetAccessPolicyQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetAccessPolicyQuery__ +* +* To run a query within a React component, call `useGetAccessPolicyQuery` and pass it any options that fit your needs. +* When your component renders, `useGetAccessPolicyQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetAccessPolicyQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAccessPolicyQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAccessPolicyDocument, baseOptions); } @@ -2853,31 +2853,31 @@ export type GetAccessPolicyQueryHookResult = ReturnType; export type GetAccessPolicyQueryResult = Apollo.QueryResult; export const GetAssetDocument = gql` - query getAsset($input: GetAssetInput!) { - getAsset(input: $input) { - Asset { - idAsset - } - } -} - `; + query getAsset($input: GetAssetInput!) { + getAsset(input: $input) { + Asset { + idAsset + } + } + } + `; /** - * __useGetAssetQuery__ - * - * To run a query within a React component, call `useGetAssetQuery` and pass it any options that fit your needs. - * When your component renders, `useGetAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetAssetQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetAssetQuery__ +* +* To run a query within a React component, call `useGetAssetQuery` and pass it any options that fit your needs. +* When your component renders, `useGetAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetAssetQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAssetQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetDocument, baseOptions); } @@ -2888,85 +2888,85 @@ export type GetAssetQueryHookResult = ReturnType; export type GetAssetLazyQueryHookResult = ReturnType; export type GetAssetQueryResult = Apollo.QueryResult; export const GetAssetVersionsDetailsDocument = gql` - query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { - getAssetVersionsDetails(input: $input) { - valid - Details { - idAssetVersion - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - Project { - idProject - Name - } - Item { - idItem - Name - EntireSubject - } - CaptureDataPhoto { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - directory - folders { - name - variantType - } - identifiers { - identifier - identifierType - } - } - Model { - idAssetVersion - authoritative - dateCreated - creationMethod - modality - purpose - units - master - directory - } - } - } -} - `; + query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { + getAssetVersionsDetails(input: $input) { + valid + Details { + idAssetVersion + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + Project { + idProject + Name + } + Item { + idItem + Name + EntireSubject + } + CaptureDataPhoto { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + directory + folders { + name + variantType + } + identifiers { + identifier + identifierType + } + } + Model { + idAssetVersion + authoritative + dateCreated + creationMethod + modality + purpose + units + master + directory + } + } + } + } + `; /** - * __useGetAssetVersionsDetailsQuery__ - * - * To run a query within a React component, call `useGetAssetVersionsDetailsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetAssetVersionsDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetAssetVersionsDetailsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetAssetVersionsDetailsQuery__ +* +* To run a query within a React component, call `useGetAssetVersionsDetailsQuery` and pass it any options that fit your needs. +* When your component renders, `useGetAssetVersionsDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetAssetVersionsDetailsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAssetVersionsDetailsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetVersionsDetailsDocument, baseOptions); } @@ -2977,33 +2977,33 @@ export type GetAssetVersionsDetailsQueryHookResult = ReturnType; export type GetAssetVersionsDetailsQueryResult = Apollo.QueryResult; export const GetContentsForAssetVersionsDocument = gql` - query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { - getContentsForAssetVersions(input: $input) { - AssetVersionContent { - idAssetVersion - folders - all - } - } -} - `; + query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { + getContentsForAssetVersions(input: $input) { + AssetVersionContent { + idAssetVersion + folders + all + } + } + } + `; /** - * __useGetContentsForAssetVersionsQuery__ - * - * To run a query within a React component, call `useGetContentsForAssetVersionsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetContentsForAssetVersionsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetContentsForAssetVersionsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetContentsForAssetVersionsQuery__ +* +* To run a query within a React component, call `useGetContentsForAssetVersionsQuery` and pass it any options that fit your needs. +* When your component renders, `useGetContentsForAssetVersionsQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetContentsForAssetVersionsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetContentsForAssetVersionsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetContentsForAssetVersionsDocument, baseOptions); } @@ -3014,39 +3014,39 @@ export type GetContentsForAssetVersionsQueryHookResult = ReturnType; export type GetContentsForAssetVersionsQueryResult = Apollo.QueryResult; export const GetUploadedAssetVersionDocument = gql` - query getUploadedAssetVersion { - getUploadedAssetVersion { - AssetVersion { - idAssetVersion - StorageSize - Asset { - idAsset - FileName - VAssetType { - idVocabulary - Term - } - } - } - } -} - `; + query getUploadedAssetVersion { + getUploadedAssetVersion { + AssetVersion { + idAssetVersion + StorageSize + FileName + Asset { + idAsset + VAssetType { + idVocabulary + Term + } + } + } + } + } + `; /** - * __useGetUploadedAssetVersionQuery__ - * - * To run a query within a React component, call `useGetUploadedAssetVersionQuery` and pass it any options that fit your needs. - * When your component renders, `useGetUploadedAssetVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetUploadedAssetVersionQuery({ - * variables: { - * }, - * }); - */ +* __useGetUploadedAssetVersionQuery__ +* +* To run a query within a React component, call `useGetUploadedAssetVersionQuery` and pass it any options that fit your needs. +* When your component renders, `useGetUploadedAssetVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetUploadedAssetVersionQuery({ + * variables: { + * }, + * }); + */ export function useGetUploadedAssetVersionQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUploadedAssetVersionDocument, baseOptions); } @@ -3057,31 +3057,31 @@ export type GetUploadedAssetVersionQueryHookResult = ReturnType; export type GetUploadedAssetVersionQueryResult = Apollo.QueryResult; export const GetCaptureDataDocument = gql` - query getCaptureData($input: GetCaptureDataInput!) { - getCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } -} - `; + query getCaptureData($input: GetCaptureDataInput!) { + getCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } + } + `; /** - * __useGetCaptureDataQuery__ - * - * To run a query within a React component, call `useGetCaptureDataQuery` and pass it any options that fit your needs. - * When your component renders, `useGetCaptureDataQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetCaptureDataQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetCaptureDataQuery__ +* +* To run a query within a React component, call `useGetCaptureDataQuery` and pass it any options that fit your needs. +* When your component renders, `useGetCaptureDataQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetCaptureDataQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetCaptureDataQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCaptureDataDocument, baseOptions); } @@ -3092,31 +3092,31 @@ export type GetCaptureDataQueryHookResult = ReturnType; export type GetCaptureDataQueryResult = Apollo.QueryResult; export const GetCaptureDataPhotoDocument = gql` - query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { - getCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } -} - `; + query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { + getCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } + } + `; /** - * __useGetCaptureDataPhotoQuery__ - * - * To run a query within a React component, call `useGetCaptureDataPhotoQuery` and pass it any options that fit your needs. - * When your component renders, `useGetCaptureDataPhotoQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetCaptureDataPhotoQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetCaptureDataPhotoQuery__ +* +* To run a query within a React component, call `useGetCaptureDataPhotoQuery` and pass it any options that fit your needs. +* When your component renders, `useGetCaptureDataPhotoQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetCaptureDataPhotoQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetCaptureDataPhotoQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCaptureDataPhotoDocument, baseOptions); } @@ -3127,29 +3127,29 @@ export type GetCaptureDataPhotoQueryHookResult = ReturnType; export type GetCaptureDataPhotoQueryResult = Apollo.QueryResult; export const AreCameraSettingsUniformDocument = gql` - query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { - areCameraSettingsUniform(input: $input) { - isUniform - } -} - `; + query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { + areCameraSettingsUniform(input: $input) { + isUniform + } + } + `; /** - * __useAreCameraSettingsUniformQuery__ - * - * To run a query within a React component, call `useAreCameraSettingsUniformQuery` and pass it any options that fit your needs. - * When your component renders, `useAreCameraSettingsUniformQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useAreCameraSettingsUniformQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useAreCameraSettingsUniformQuery__ +* +* To run a query within a React component, call `useAreCameraSettingsUniformQuery` and pass it any options that fit your needs. +* When your component renders, `useAreCameraSettingsUniformQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useAreCameraSettingsUniformQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useAreCameraSettingsUniformQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(AreCameraSettingsUniformDocument, baseOptions); } @@ -3160,31 +3160,31 @@ export type AreCameraSettingsUniformQueryHookResult = ReturnType; export type AreCameraSettingsUniformQueryResult = Apollo.QueryResult; export const GetLicenseDocument = gql` - query getLicense($input: GetLicenseInput!) { - getLicense(input: $input) { - License { - idLicense - } - } -} - `; + query getLicense($input: GetLicenseInput!) { + getLicense(input: $input) { + License { + idLicense + } + } + } + `; /** - * __useGetLicenseQuery__ - * - * To run a query within a React component, call `useGetLicenseQuery` and pass it any options that fit your needs. - * When your component renders, `useGetLicenseQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetLicenseQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetLicenseQuery__ +* +* To run a query within a React component, call `useGetLicenseQuery` and pass it any options that fit your needs. +* When your component renders, `useGetLicenseQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetLicenseQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetLicenseQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetLicenseDocument, baseOptions); } @@ -3195,31 +3195,31 @@ export type GetLicenseQueryHookResult = ReturnType; export type GetLicenseLazyQueryHookResult = ReturnType; export type GetLicenseQueryResult = Apollo.QueryResult; export const GetModelDocument = gql` - query getModel($input: GetModelInput!) { - getModel(input: $input) { - Model { - idModel - } - } -} - `; + query getModel($input: GetModelInput!) { + getModel(input: $input) { + Model { + idModel + } + } + } + `; /** - * __useGetModelQuery__ - * - * To run a query within a React component, call `useGetModelQuery` and pass it any options that fit your needs. - * When your component renders, `useGetModelQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetModelQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetModelQuery__ +* +* To run a query within a React component, call `useGetModelQuery` and pass it any options that fit your needs. +* When your component renders, `useGetModelQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetModelQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetModelQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetModelDocument, baseOptions); } @@ -3230,38 +3230,38 @@ export type GetModelQueryHookResult = ReturnType; export type GetModelLazyQueryHookResult = ReturnType; export type GetModelQueryResult = Apollo.QueryResult; export const GetObjectChildrenDocument = gql` - query getObjectChildren($input: GetObjectChildrenInput!) { - getObjectChildren(input: $input) { - success - error - entries { - idSystemObject - name - objectType - idObject - metadata - } - metadataColumns - } -} - `; + query getObjectChildren($input: GetObjectChildrenInput!) { + getObjectChildren(input: $input) { + success + error + entries { + idSystemObject + name + objectType + idObject + metadata + } + metadataColumns + } + } + `; /** - * __useGetObjectChildrenQuery__ - * - * To run a query within a React component, call `useGetObjectChildrenQuery` and pass it any options that fit your needs. - * When your component renders, `useGetObjectChildrenQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetObjectChildrenQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetObjectChildrenQuery__ +* +* To run a query within a React component, call `useGetObjectChildrenQuery` and pass it any options that fit your needs. +* When your component renders, `useGetObjectChildrenQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetObjectChildrenQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetObjectChildrenQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetObjectChildrenDocument, baseOptions); } @@ -3272,31 +3272,31 @@ export type GetObjectChildrenQueryHookResult = ReturnType; export type GetObjectChildrenQueryResult = Apollo.QueryResult; export const GetIntermediaryFileDocument = gql` - query getIntermediaryFile($input: GetIntermediaryFileInput!) { - getIntermediaryFile(input: $input) { - IntermediaryFile { - idIntermediaryFile - } - } -} - `; + query getIntermediaryFile($input: GetIntermediaryFileInput!) { + getIntermediaryFile(input: $input) { + IntermediaryFile { + idIntermediaryFile + } + } + } + `; /** - * __useGetIntermediaryFileQuery__ - * - * To run a query within a React component, call `useGetIntermediaryFileQuery` and pass it any options that fit your needs. - * When your component renders, `useGetIntermediaryFileQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetIntermediaryFileQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetIntermediaryFileQuery__ +* +* To run a query within a React component, call `useGetIntermediaryFileQuery` and pass it any options that fit your needs. +* When your component renders, `useGetIntermediaryFileQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetIntermediaryFileQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIntermediaryFileQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIntermediaryFileDocument, baseOptions); } @@ -3307,31 +3307,31 @@ export type GetIntermediaryFileQueryHookResult = ReturnType; export type GetIntermediaryFileQueryResult = Apollo.QueryResult; export const GetSceneDocument = gql` - query getScene($input: GetSceneInput!) { - getScene(input: $input) { - Scene { - idScene - } - } -} - `; + query getScene($input: GetSceneInput!) { + getScene(input: $input) { + Scene { + idScene + } + } + } + `; /** - * __useGetSceneQuery__ - * - * To run a query within a React component, call `useGetSceneQuery` and pass it any options that fit your needs. - * When your component renders, `useGetSceneQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetSceneQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetSceneQuery__ +* +* To run a query within a React component, call `useGetSceneQuery` and pass it any options that fit your needs. +* When your component renders, `useGetSceneQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetSceneQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSceneQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSceneDocument, baseOptions); } @@ -3342,33 +3342,33 @@ export type GetSceneQueryHookResult = ReturnType; export type GetSceneLazyQueryHookResult = ReturnType; export type GetSceneQueryResult = Apollo.QueryResult; export const GetIngestionItemsForSubjectsDocument = gql` - query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { - getIngestionItemsForSubjects(input: $input) { - Item { - idItem - EntireSubject - Name - } - } -} - `; + query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { + getIngestionItemsForSubjects(input: $input) { + Item { + idItem + EntireSubject + Name + } + } + } + `; /** - * __useGetIngestionItemsForSubjectsQuery__ - * - * To run a query within a React component, call `useGetIngestionItemsForSubjectsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetIngestionItemsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetIngestionItemsForSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetIngestionItemsForSubjectsQuery__ +* +* To run a query within a React component, call `useGetIngestionItemsForSubjectsQuery` and pass it any options that fit your needs. +* When your component renders, `useGetIngestionItemsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetIngestionItemsForSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIngestionItemsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIngestionItemsForSubjectsDocument, baseOptions); } @@ -3379,32 +3379,32 @@ export type GetIngestionItemsForSubjectsQueryHookResult = ReturnType; export type GetIngestionItemsForSubjectsQueryResult = Apollo.QueryResult; export const GetIngestionProjectsForSubjectsDocument = gql` - query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { - getIngestionProjectsForSubjects(input: $input) { - Project { - idProject - Name - } - } -} - `; + query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { + getIngestionProjectsForSubjects(input: $input) { + Project { + idProject + Name + } + } + } + `; /** - * __useGetIngestionProjectsForSubjectsQuery__ - * - * To run a query within a React component, call `useGetIngestionProjectsForSubjectsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetIngestionProjectsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetIngestionProjectsForSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetIngestionProjectsForSubjectsQuery__ +* +* To run a query within a React component, call `useGetIngestionProjectsForSubjectsQuery` and pass it any options that fit your needs. +* When your component renders, `useGetIngestionProjectsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetIngestionProjectsForSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } @@ -3415,31 +3415,31 @@ export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; export type GetIngestionProjectsForSubjectsQueryResult = Apollo.QueryResult; export const GetItemDocument = gql` - query getItem($input: GetItemInput!) { - getItem(input: $input) { - Item { - idItem - } - } -} - `; + query getItem($input: GetItemInput!) { + getItem(input: $input) { + Item { + idItem + } + } + } + `; /** - * __useGetItemQuery__ - * - * To run a query within a React component, call `useGetItemQuery` and pass it any options that fit your needs. - * When your component renders, `useGetItemQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetItemQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetItemQuery__ +* +* To run a query within a React component, call `useGetItemQuery` and pass it any options that fit your needs. +* When your component renders, `useGetItemQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetItemQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetItemQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetItemDocument, baseOptions); } @@ -3450,32 +3450,32 @@ export type GetItemQueryHookResult = ReturnType; export type GetItemLazyQueryHookResult = ReturnType; export type GetItemQueryResult = Apollo.QueryResult; export const GetItemsForSubjectDocument = gql` - query getItemsForSubject($input: GetItemsForSubjectInput!) { - getItemsForSubject(input: $input) { - Item { - idItem - Name - } - } -} - `; + query getItemsForSubject($input: GetItemsForSubjectInput!) { + getItemsForSubject(input: $input) { + Item { + idItem + Name + } + } + } + `; /** - * __useGetItemsForSubjectQuery__ - * - * To run a query within a React component, call `useGetItemsForSubjectQuery` and pass it any options that fit your needs. - * When your component renders, `useGetItemsForSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetItemsForSubjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetItemsForSubjectQuery__ +* +* To run a query within a React component, call `useGetItemsForSubjectQuery` and pass it any options that fit your needs. +* When your component renders, `useGetItemsForSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetItemsForSubjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetItemsForSubjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetItemsForSubjectDocument, baseOptions); } @@ -3486,53 +3486,53 @@ export type GetItemsForSubjectQueryHookResult = ReturnType; export type GetItemsForSubjectQueryResult = Apollo.QueryResult; export const GetObjectsForItemDocument = gql` - query getObjectsForItem($input: GetObjectsForItemInput!) { - getObjectsForItem(input: $input) { - CaptureData { - idCaptureData - DateCaptured - Description - } - Model { - idModel - Authoritative - DateCreated - } - Scene { - idScene - HasBeenQCd - IsOriented - Name - } - IntermediaryFile { - idIntermediaryFile - DateCreated - } - ProjectDocumentation { - idProjectDocumentation - Description - Name - } - } -} - `; + query getObjectsForItem($input: GetObjectsForItemInput!) { + getObjectsForItem(input: $input) { + CaptureData { + idCaptureData + DateCaptured + Description + } + Model { + idModel + Authoritative + DateCreated + } + Scene { + idScene + HasBeenQCd + IsOriented + Name + } + IntermediaryFile { + idIntermediaryFile + DateCreated + } + ProjectDocumentation { + idProjectDocumentation + Description + Name + } + } + } + `; /** - * __useGetObjectsForItemQuery__ - * - * To run a query within a React component, call `useGetObjectsForItemQuery` and pass it any options that fit your needs. - * When your component renders, `useGetObjectsForItemQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetObjectsForItemQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetObjectsForItemQuery__ +* +* To run a query within a React component, call `useGetObjectsForItemQuery` and pass it any options that fit your needs. +* When your component renders, `useGetObjectsForItemQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetObjectsForItemQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetObjectsForItemQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetObjectsForItemDocument, baseOptions); } @@ -3543,31 +3543,31 @@ export type GetObjectsForItemQueryHookResult = ReturnType; export type GetObjectsForItemQueryResult = Apollo.QueryResult; export const GetProjectDocument = gql` - query getProject($input: GetProjectInput!) { - getProject(input: $input) { - Project { - idProject - } - } -} - `; + query getProject($input: GetProjectInput!) { + getProject(input: $input) { + Project { + idProject + } + } + } + `; /** - * __useGetProjectQuery__ - * - * To run a query within a React component, call `useGetProjectQuery` and pass it any options that fit your needs. - * When your component renders, `useGetProjectQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetProjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetProjectQuery__ +* +* To run a query within a React component, call `useGetProjectQuery` and pass it any options that fit your needs. +* When your component renders, `useGetProjectQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetProjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetProjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetProjectDocument, baseOptions); } @@ -3578,31 +3578,31 @@ export type GetProjectQueryHookResult = ReturnType; export type GetProjectLazyQueryHookResult = ReturnType; export type GetProjectQueryResult = Apollo.QueryResult; export const GetProjectDocumentationDocument = gql` - query getProjectDocumentation($input: GetProjectDocumentationInput!) { - getProjectDocumentation(input: $input) { - ProjectDocumentation { - idProjectDocumentation - } - } -} - `; + query getProjectDocumentation($input: GetProjectDocumentationInput!) { + getProjectDocumentation(input: $input) { + ProjectDocumentation { + idProjectDocumentation + } + } + } + `; /** - * __useGetProjectDocumentationQuery__ - * - * To run a query within a React component, call `useGetProjectDocumentationQuery` and pass it any options that fit your needs. - * When your component renders, `useGetProjectDocumentationQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetProjectDocumentationQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetProjectDocumentationQuery__ +* +* To run a query within a React component, call `useGetProjectDocumentationQuery` and pass it any options that fit your needs. +* When your component renders, `useGetProjectDocumentationQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetProjectDocumentationQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetProjectDocumentationQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetProjectDocumentationDocument, baseOptions); } @@ -3613,31 +3613,31 @@ export type GetProjectDocumentationQueryHookResult = ReturnType; export type GetProjectDocumentationQueryResult = Apollo.QueryResult; export const GetSubjectDocument = gql` - query getSubject($input: GetSubjectInput!) { - getSubject(input: $input) { - Subject { - idSubject - } - } -} - `; + query getSubject($input: GetSubjectInput!) { + getSubject(input: $input) { + Subject { + idSubject + } + } + } + `; /** - * __useGetSubjectQuery__ - * - * To run a query within a React component, call `useGetSubjectQuery` and pass it any options that fit your needs. - * When your component renders, `useGetSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetSubjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetSubjectQuery__ +* +* To run a query within a React component, call `useGetSubjectQuery` and pass it any options that fit your needs. +* When your component renders, `useGetSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetSubjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSubjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSubjectDocument, baseOptions); } @@ -3648,32 +3648,32 @@ export type GetSubjectQueryHookResult = ReturnType; export type GetSubjectLazyQueryHookResult = ReturnType; export type GetSubjectQueryResult = Apollo.QueryResult; export const GetSubjectsForUnitDocument = gql` - query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { - getSubjectsForUnit(input: $input) { - Subject { - idSubject - Name - } - } -} - `; + query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { + getSubjectsForUnit(input: $input) { + Subject { + idSubject + Name + } + } + } + `; /** - * __useGetSubjectsForUnitQuery__ - * - * To run a query within a React component, call `useGetSubjectsForUnitQuery` and pass it any options that fit your needs. - * When your component renders, `useGetSubjectsForUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetSubjectsForUnitQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetSubjectsForUnitQuery__ +* +* To run a query within a React component, call `useGetSubjectsForUnitQuery` and pass it any options that fit your needs. +* When your component renders, `useGetSubjectsForUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetSubjectsForUnitQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSubjectsForUnitQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSubjectsForUnitDocument, baseOptions); } @@ -3684,31 +3684,31 @@ export type GetSubjectsForUnitQueryHookResult = ReturnType; export type GetSubjectsForUnitQueryResult = Apollo.QueryResult; export const GetUnitDocument = gql` - query getUnit($input: GetUnitInput!) { - getUnit(input: $input) { - Unit { - idUnit - } - } -} - `; + query getUnit($input: GetUnitInput!) { + getUnit(input: $input) { + Unit { + idUnit + } + } + } + `; /** - * __useGetUnitQuery__ - * - * To run a query within a React component, call `useGetUnitQuery` and pass it any options that fit your needs. - * When your component renders, `useGetUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetUnitQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetUnitQuery__ +* +* To run a query within a React component, call `useGetUnitQuery` and pass it any options that fit your needs. +* When your component renders, `useGetUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetUnitQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetUnitQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUnitDocument, baseOptions); } @@ -3719,35 +3719,35 @@ export type GetUnitQueryHookResult = ReturnType; export type GetUnitLazyQueryHookResult = ReturnType; export type GetUnitQueryResult = Apollo.QueryResult; export const SearchIngestionSubjectsDocument = gql` - query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { - searchIngestionSubjects(input: $input) { - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - } -} - `; + query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { + searchIngestionSubjects(input: $input) { + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + } + } + `; /** - * __useSearchIngestionSubjectsQuery__ - * - * To run a query within a React component, call `useSearchIngestionSubjectsQuery` and pass it any options that fit your needs. - * When your component renders, `useSearchIngestionSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useSearchIngestionSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useSearchIngestionSubjectsQuery__ +* +* To run a query within a React component, call `useSearchIngestionSubjectsQuery` and pass it any options that fit your needs. +* When your component renders, `useSearchIngestionSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useSearchIngestionSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useSearchIngestionSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(SearchIngestionSubjectsDocument, baseOptions); } @@ -3758,38 +3758,38 @@ export type SearchIngestionSubjectsQueryHookResult = ReturnType; export type SearchIngestionSubjectsQueryResult = Apollo.QueryResult; export const GetCurrentUserDocument = gql` - query getCurrentUser { - getCurrentUser { - User { - idUser - Name - Active - DateActivated - DateDisabled - EmailAddress - EmailSettings - SecurityID - WorkflowNotificationTime - } - } -} - `; + query getCurrentUser { + getCurrentUser { + User { + idUser + Name + Active + DateActivated + DateDisabled + EmailAddress + EmailSettings + SecurityID + WorkflowNotificationTime + } + } + } + `; /** - * __useGetCurrentUserQuery__ - * - * To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs. - * When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetCurrentUserQuery({ - * variables: { - * }, - * }); - */ +* __useGetCurrentUserQuery__ +* +* To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs. +* When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetCurrentUserQuery({ + * variables: { + * }, + * }); + */ export function useGetCurrentUserQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCurrentUserDocument, baseOptions); } @@ -3800,34 +3800,34 @@ export type GetCurrentUserQueryHookResult = ReturnType; export type GetCurrentUserQueryResult = Apollo.QueryResult; export const GetUserDocument = gql` - query getUser($input: GetUserInput!) { - getUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } -} - `; + query getUser($input: GetUserInput!) { + getUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } + } + `; /** - * __useGetUserQuery__ - * - * To run a query within a React component, call `useGetUserQuery` and pass it any options that fit your needs. - * When your component renders, `useGetUserQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetUserQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetUserQuery__ +* +* To run a query within a React component, call `useGetUserQuery` and pass it any options that fit your needs. +* When your component renders, `useGetUserQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetUserQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetUserQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUserDocument, baseOptions); } @@ -3838,31 +3838,31 @@ export type GetUserQueryHookResult = ReturnType; export type GetUserLazyQueryHookResult = ReturnType; export type GetUserQueryResult = Apollo.QueryResult; export const GetVocabularyDocument = gql` - query getVocabulary($input: GetVocabularyInput!) { - getVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } -} - `; + query getVocabulary($input: GetVocabularyInput!) { + getVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } + } + `; /** - * __useGetVocabularyQuery__ - * - * To run a query within a React component, call `useGetVocabularyQuery` and pass it any options that fit your needs. - * When your component renders, `useGetVocabularyQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetVocabularyQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetVocabularyQuery__ +* +* To run a query within a React component, call `useGetVocabularyQuery` and pass it any options that fit your needs. +* When your component renders, `useGetVocabularyQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetVocabularyQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetVocabularyQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetVocabularyDocument, baseOptions); } @@ -3873,35 +3873,35 @@ export type GetVocabularyQueryHookResult = ReturnType; export type GetVocabularyQueryResult = Apollo.QueryResult; export const GetVocabularyEntriesDocument = gql` - query getVocabularyEntries($input: GetVocabularyEntriesInput!) { - getVocabularyEntries(input: $input) { - VocabularyEntries { - eVocabSetID - Vocabulary { - idVocabulary - Term - } - } - } -} - `; + query getVocabularyEntries($input: GetVocabularyEntriesInput!) { + getVocabularyEntries(input: $input) { + VocabularyEntries { + eVocabSetID + Vocabulary { + idVocabulary + Term + } + } + } + } + `; /** - * __useGetVocabularyEntriesQuery__ - * - * To run a query within a React component, call `useGetVocabularyEntriesQuery` and pass it any options that fit your needs. - * When your component renders, `useGetVocabularyEntriesQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetVocabularyEntriesQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetVocabularyEntriesQuery__ +* +* To run a query within a React component, call `useGetVocabularyEntriesQuery` and pass it any options that fit your needs. +* When your component renders, `useGetVocabularyEntriesQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetVocabularyEntriesQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetVocabularyEntriesQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetVocabularyEntriesDocument, baseOptions); } @@ -3912,31 +3912,31 @@ export type GetVocabularyEntriesQueryHookResult = ReturnType; export type GetVocabularyEntriesQueryResult = Apollo.QueryResult; export const GetWorkflowDocument = gql` - query getWorkflow($input: GetWorkflowInput!) { - getWorkflow(input: $input) { - Workflow { - idWorkflow - } - } -} - `; + query getWorkflow($input: GetWorkflowInput!) { + getWorkflow(input: $input) { + Workflow { + idWorkflow + } + } + } + `; /** - * __useGetWorkflowQuery__ - * - * To run a query within a React component, call `useGetWorkflowQuery` and pass it any options that fit your needs. - * When your component renders, `useGetWorkflowQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetWorkflowQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ +* __useGetWorkflowQuery__ +* +* To run a query within a React component, call `useGetWorkflowQuery` and pass it any options that fit your needs. +* When your component renders, `useGetWorkflowQuery` returns an object from Apollo Client that contains loading, error, and data properties +* you can use to render your UI. +* +* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; +* +* @example +* const { data, loading, error } = useGetWorkflowQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetWorkflowQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetWorkflowDocument, baseOptions); } diff --git a/server/graphql/api/queries/asset/getUploadedAssetVersion.ts b/server/graphql/api/queries/asset/getUploadedAssetVersion.ts index 252dd5043..4243f8cad 100644 --- a/server/graphql/api/queries/asset/getUploadedAssetVersion.ts +++ b/server/graphql/api/queries/asset/getUploadedAssetVersion.ts @@ -6,9 +6,9 @@ const getUploadedAssetVersion = gql` AssetVersion { idAssetVersion StorageSize + FileName Asset { idAsset - FileName VAssetType { idVocabulary Term From 0c83b0d19e503c843e5d36dda1db63924af05502 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 28 Sep 2020 13:35:23 +0530 Subject: [PATCH 02/22] remove unused components --- .../RepositoryMetadataView/index.tsx | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 client/src/pages/Repository/components/RepositoryMetadataView/index.tsx diff --git a/client/src/pages/Repository/components/RepositoryMetadataView/index.tsx b/client/src/pages/Repository/components/RepositoryMetadataView/index.tsx deleted file mode 100644 index d6a878279..000000000 --- a/client/src/pages/Repository/components/RepositoryMetadataView/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { - TableContainer, - Table, - TableHead, - TableRow, - TableCell, - TableBody, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; - -const useStyles = makeStyles(({ palette }) => ({ - container: { - display: 'flex', - flex: 1, - marginLeft: 10, - border: `1px solid ${palette.primary.light}`, - borderRadius: 10, - }, - header: { - position: 'sticky', - top: 0, - backgroundColor: palette.background.paper, - color: palette.primary.contrastText - }, - body: { - overflow: 'auto' - }, -})); - -function RepositoryMetadataView(): React.ReactElement { - const classes = useStyles(); - const header: string[] = ['Project', 'Subject ID', 'Item Name']; - - return ( - - - - - {header.map((label, index) => {label})} - - - - -
-
- ); -} - -export default RepositoryMetadataView; From e5e62ecb2c8388da134121c92fafe17366742c80 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 28 Sep 2020 16:37:00 +0530 Subject: [PATCH 03/22] show treeview in scroll --- .../RepositoryTreeHeader.tsx | 3 + .../RepositoryTreeView/StyledTreeItem.tsx | 79 +++++++++------ .../RepositoryTreeView/TreeViewContents.tsx | 17 +++- .../components/RepositoryTreeView/index.tsx | 95 +++++++++++-------- client/src/utils/repository.ts | 6 +- 5 files changed, 121 insertions(+), 79 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 1a4bdfadd..14bef2c81 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -9,6 +9,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ height: 50, background: palette.primary.light, borderRadius: 5, + width: '100vw', margin: '0px 0px 5px 0px', position: 'sticky', top: 0, @@ -20,6 +21,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ treeView: { display: 'flex', flex: 1, + position: 'sticky', + left: 20, alignItems: 'center', marginLeft: 20, color: palette.primary.dark, diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 8663b300c..fe9137491 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -1,4 +1,4 @@ -import { Box, Collapse } from '@material-ui/core'; +import { Box, Collapse, Tooltip } from '@material-ui/core'; import { fade, withStyles, Theme, makeStyles } from '@material-ui/core/styles'; import { TreeItem, TreeItemProps } from '@material-ui/lab'; import React from 'react'; @@ -33,20 +33,21 @@ interface StyledTreeItemProps { const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) => ({ iconContainer: { width: 25, - '& .close': { - opacity: 0.3 - }, marginLeft: 5, + position: 'sticky', + left: 10, [breakpoints.down('lg')]: { width: 15, marginLeft: 8, - } + }, + '& .close': { + opacity: 0.3 + }, }, root: { marginTop: 5, }, group: { - marginLeft: 10, paddingLeft: 20, borderLeft: `1px dashed ${fade(palette.text.primary, 0.2)}`, [breakpoints.down('lg')]: { @@ -81,15 +82,45 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) } }))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); -const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ +interface TreeLabelProps { + label?: React.ReactNode; + metadata: string[]; +} + +const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ + label: { + display: 'flex', + flex: 1, + alignItems: 'center', + position: 'sticky', + left: 45, + [breakpoints.down('lg')]: { + left: 35, + }, + } +})); + +function TreeLabel(props: TreeLabelProps): React.ReactElement { + const classes = useTreeLabelStyles(); + const { label, metadata } = props; + + return ( + + {label} + + + ); +} + +const useMetadataStyles = makeStyles(({ palette, typography, breakpoints }) => ({ metadata: { display: 'flex', - width: '35vw', + width: '50vw', [breakpoints.down('lg')]: { width: '42vw', } }, - text: { + column: { display: 'flex', alignItems: 'center', fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(18) : undefined, @@ -103,22 +134,6 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface TreeLabelProps { - label?: React.ReactNode; - metadata: string[]; -} - -function TreeLabel(props: TreeLabelProps): React.ReactElement { - const { label, metadata } = props; - - return ( - - {label} - - - ); -} - interface MetadataViewProps { header: boolean; metadata: string[]; @@ -126,14 +141,20 @@ interface MetadataViewProps { export function MetadataView(props: MetadataViewProps): React.ReactElement { const { metadata } = props; - const classes = useStyles(props); + const classes = useMetadataStyles(props); const [unit, subjectId, itemName] = metadata; return ( - {unit} - {trimmedMetadataField(subjectId)} - {itemName} + + {unit} + + + {trimmedMetadataField(subjectId, 25, 10)} + + + {trimmedMetadataField(itemName, 15, 5)} + ); } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx index 87a66e405..65e6b9458 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx @@ -7,21 +7,28 @@ import { getTermForSystemObjectType } from '../../../../utils/repository'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', - height: 50, - width: '10%', + height: 40, + marginLeft: 20, + position: 'sticky', + left: 20, alignItems: 'center', - justifyContent: 'center', + [breakpoints.down('lg')]: { + height: 30, + }, }, emptyList: { display: 'flex', - padding: '10px 5px', + height: 40, + padding: '10px 15px', alignItems: 'center', color: palette.grey[400], [breakpoints.down('lg')]: { - height: 40, + height: 25, }, }, emptyListText: { + position: 'sticky', + left: 15, [breakpoints.down('lg')]: { fontSize: 12, }, diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 4ed64a459..cfff6bbcc 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -15,11 +15,19 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flex: 5, maxHeight: '72vh', + maxWidth: '83vw', flexDirection: 'column', overflow: 'auto', [breakpoints.down('lg')]: { - maxHeight: '70vh', - } + maxHeight: '71vh', + maxWidth: '80vw', + }, + }, + tree: { + display: 'flex', + flexDirection: 'column', + flex: 1, + width: '100vw', }, fullView: { display: 'flex', @@ -46,50 +54,53 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const entries = getSortedTreeEntries(getRootObjectsData?.getObjectChildren?.entries ?? []); return ( - } - defaultExpandIcon={} - > - - {noFilter && ( - - Please select a valid filter - - )} - {!getRootObjectsLoading && !getRootObjectsError ? ( - <> - {entries.map((entry, index: number) => { - const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = index % 2 ? RepositoryColorVariant.Light : RepositoryColorVariant.Dark; - const { icon, color } = getObjectInterfaceDetails(objectType, variant); + - return ( - - ); - })} - - ) - : ( + } + defaultExpandIcon={} + > + + {noFilter && ( + + Please select a valid filter + + )} + {!getRootObjectsLoading && !getRootObjectsError ? ( <> - {!noFilter && ( - - - - )} + {entries.map((entry, index: number) => { + const { idSystemObject, name, objectType, idObject, metadata } = entry; + const variant = index % 2 ? RepositoryColorVariant.Light : RepositoryColorVariant.Dark; + const { icon, color } = getObjectInterfaceDetails(objectType, variant); + + return ( + + ); + })} - )} + ) + : ( + <> + {!noFilter && ( + + + + )} + + )} - + + ); } diff --git a/client/src/utils/repository.ts b/client/src/utils/repository.ts index cf80c2cb2..00a738ae8 100644 --- a/client/src/utils/repository.ts +++ b/client/src/utils/repository.ts @@ -58,8 +58,8 @@ export function getSortedTreeEntries(entries: NavigationResultEntry[]): Navigati return lodash.orderBy(entries, [(entry: NavigationResultEntry) => entry.name.toLowerCase()], 'asc'); } -export function trimmedMetadataField(value: string): string { +export function trimmedMetadataField(value: string, start: number, end: number): string { const { length } = value; - if (length < 40) return value; - return `${value.substring(0, 25)}.....${value.substring(length - 10, length)}`; + if (length < 30) return value; + return `${value.substring(0, start)}...${value.substring(length - end, length)}`; } From 27f0f930ab20203c77cff5f4b7a39ebb361762ac Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 30 Sep 2020 14:23:47 +0530 Subject: [PATCH 04/22] icons for repository tree and filter --- .../components/controls/RepositoryIcon.tsx | 46 ++++++++++++++ client/src/components/index.ts | 3 +- .../components/RepositoryFilterView/index.tsx | 60 ++++++++++++------- .../RepositoryTreeView/RepositoryTreeNode.tsx | 24 ++++---- .../RepositoryTreeView/StyledTreeItem.tsx | 18 ++++-- .../RepositoryTreeView/TreeViewContents.tsx | 3 +- .../components/RepositoryTreeView/index.tsx | 2 +- client/src/theme/colors.ts | 55 +++++++++++++---- client/src/theme/index.ts | 2 +- 9 files changed, 160 insertions(+), 53 deletions(-) create mode 100644 client/src/components/controls/RepositoryIcon.tsx diff --git a/client/src/components/controls/RepositoryIcon.tsx b/client/src/components/controls/RepositoryIcon.tsx new file mode 100644 index 000000000..b9ebfabbb --- /dev/null +++ b/client/src/components/controls/RepositoryIcon.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { eSystemObjectType } from '../../types/server'; +import { getTermForSystemObjectType } from '../../utils/repository'; + +const useStyles = makeStyles(({ typography, breakpoints }) => ({ + container: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: 20, + width: 20, + borderRadius: 2.5, + backgroundColor: ({ backgroundColor }: RepositoryIconProps) => backgroundColor, + [breakpoints.down('lg')]: { + height: 15, + width: 15, + }, + }, + initial: { + fontSize: 10, + fontWeight: typography.fontWeightBold, + color: ({ textColor }: RepositoryIconProps) => textColor, + } +})); + +interface RepositoryIconProps { + objectType: eSystemObjectType; + backgroundColor: string; + textColor: string; +} + +function RepositoryIcon(props: RepositoryIconProps): React.ReactElement { + const { objectType } = props; + const classes = useStyles(props); + const initial = getTermForSystemObjectType(objectType).toUpperCase().slice(0, 1); + + return ( + + {initial} + + ); +} + +export default RepositoryIcon; \ No newline at end of file diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 0059ab205..959b32994 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -8,6 +8,7 @@ import PublicRoute from './shared/PublicRoute'; import FieldType from './shared/FieldType'; import Loader from './shared/Loader'; import LoadingButton from './controls/LoadingButton'; +import RepositoryIcon from './controls/RepositoryIcon'; export * from './shared/Sidebar'; -export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader }; +export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, RepositoryIcon }; diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index ae17b703e..1548cda85 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -3,8 +3,10 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { RepositoryFilter } from '../../index'; import { DebounceInput } from 'react-debounce-input'; -import { HiCheckCircle } from 'react-icons/hi'; import { motion } from 'framer-motion'; +import { eSystemObjectType } from '../../../../types/server'; +import { RepositoryIcon } from '../../../../components'; +import { Colors, palette } from '../../../../theme'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -16,7 +18,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ marginBottom: 10, [breakpoints.down('lg')]: { padding: 10, - borderRadius: 5, + borderRadius: 5 } }, search: { @@ -32,7 +34,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ [breakpoints.down('lg')]: { height: 20, fontSize: 14, - padding: '5px 0px', + padding: '5px 0px' }, '&::placeholder': { fontStyle: 'italic' @@ -54,28 +56,28 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ [breakpoints.down('lg')]: { minWidth: 100, width: 100, - padding: '5px 10px', + padding: '5px 10px' }, '&:not(:first-child)': { marginLeft: 10 - }, + } }, filterSelected: { color: palette.background.paper, - background: palette.primary.main, + background: palette.primary.main }, filterText: { marginLeft: 10, [breakpoints.down('lg')]: { marginLeft: 5, fontSize: 10 - }, + } } })); interface RepositoryFilterViewProps { filter: RepositoryFilter; - onChange: (field: string, value: string | boolean) => void + onChange: (field: string, value: string | boolean) => void; } function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElement { @@ -85,11 +87,13 @@ function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElem const CheckboxFilters = [ { value: filter.units, - name: 'units' + name: 'units', + type: eSystemObjectType.eUnit }, { value: filter.projects, - name: 'projects' + name: 'projects', + type: eSystemObjectType.eProject } ]; @@ -106,19 +110,29 @@ function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElem placeholder='Search...' /> - {CheckboxFilters.map(({ value, name }, index: number) => ( - onChange(name, !value)} - whileTap={{ scale: 0.95 }} - > - - {name.toUpperCase()} - - ))} + {CheckboxFilters.map(({ value, name, type }, index: number) => { + const selected = value; + const textColor = selected ? palette.primary.main : Colors.defaults.white; + const backgroundColor = selected ? Colors.defaults.white : palette.primary.contrastText; + + const iconProps = { objectType: type, textColor, backgroundColor }; + + return ( + onChange(name, !value)} + whileTap={{ scale: 0.95 }} + > + + + {name.toUpperCase()} + + + ); + })} ); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx index b69b07cf6..e1c8a1329 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx @@ -4,11 +4,10 @@ import TreeViewContents from './TreeViewContents'; import { eSystemObjectType } from '../../../../types/server'; import { getRepositoryTreeNodeId, getSortedTreeEntries } from '../../../../utils/repository'; import { useGetObjectChildren } from '../../hooks/useRepository'; -import { FiPackage, FiBox } from 'react-icons/fi'; -import { BsArchive, BsBoundingBox } from 'react-icons/bs'; import { AiOutlineFileText } from 'react-icons/ai'; import { Colors } from '../../../../theme'; import { RepositoryColorVariant } from '../../../../theme/colors'; +import { RepositoryIcon } from '../../../../components'; interface RepositoryTreeNodeProps { idSystemObject: number; @@ -36,6 +35,7 @@ function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement color={color} onLabelClick={getObjectChildren} onIconClick={getObjectChildren} + objectType={objectType} nodeId={nodeId} label={name} metadata={metadata} @@ -43,7 +43,7 @@ function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement {entries.map((entry, index: number) => { const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = index % 2 ? RepositoryColorVariant.Light : RepositoryColorVariant.Dark; + const variant = index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; const { icon, color } = getObjectInterfaceDetails(objectType, variant); return ( @@ -70,22 +70,26 @@ type ObjectInterfaceDetails = { }; export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant: RepositoryColorVariant): ObjectInterfaceDetails { - const colorVariant = Colors.repository; + const color: string = Colors.repository[objectType][variant]; + const textColor: string = Colors.defaults.white; + const backgroundColor: string = Colors.repository[objectType][RepositoryColorVariant.dark] || Colors.repository.default[RepositoryColorVariant.dark]; + + const iconProps = { objectType, backgroundColor, textColor }; switch (objectType) { case eSystemObjectType.eUnit: - return { icon: , color: colorVariant.unit[variant] }; + return { icon: , color }; case eSystemObjectType.eProject: - return { icon: , color: colorVariant.project[variant] }; + return { icon: , color }; case eSystemObjectType.eSubject: - return { icon: , color: colorVariant.subject[variant] }; + return { icon: , color }; case eSystemObjectType.eItem: - return { icon: , color: colorVariant.item[variant] }; + return { icon: , color }; case eSystemObjectType.eCaptureData: - return { icon: , color: colorVariant.captureData[variant] }; + return { icon: , color }; default: - return { icon: , color: colorVariant.default[variant] }; + return { icon: , color: Colors.repository.default[variant] }; } } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index fe9137491..5d9f91dd5 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -3,7 +3,7 @@ import { fade, withStyles, Theme, makeStyles } from '@material-ui/core/styles'; import { TreeItem, TreeItemProps } from '@material-ui/lab'; import React from 'react'; import { animated, useSpring } from 'react-spring'; -import { trimmedMetadataField } from '../../../../utils/repository'; +import { getTermForSystemObjectType, trimmedMetadataField } from '../../../../utils/repository'; interface TransitionComponentProps { in?: boolean; @@ -28,6 +28,7 @@ function TransitionComponent(props: TransitionComponentProps): React.ReactElemen interface StyledTreeItemProps { color: string; metadata: string[]; + objectType: number; } const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) => ({ @@ -80,11 +81,12 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) selected: { backgroundColor: 'transparent' } -}))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); +}))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); interface TreeLabelProps { label?: React.ReactNode; metadata: string[]; + objectType: number; } const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ @@ -102,11 +104,17 @@ const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ function TreeLabel(props: TreeLabelProps): React.ReactElement { const classes = useTreeLabelStyles(); - const { label, metadata } = props; + const { label, metadata, objectType } = props; + + const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; return ( - {label} + + + {label} + + ); @@ -150,7 +158,7 @@ export function MetadataView(props: MetadataViewProps): React.ReactElement { {unit} - {trimmedMetadataField(subjectId, 25, 10)} + {trimmedMetadataField(subjectId, 20, 10)} {trimmedMetadataField(itemName, 15, 5)} diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx index 65e6b9458..9d0be294d 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx @@ -19,11 +19,10 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ emptyList: { display: 'flex', height: 40, - padding: '10px 15px', alignItems: 'center', color: palette.grey[400], [breakpoints.down('lg')]: { - height: 25, + height: 30, }, }, emptyListText: { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index cfff6bbcc..042e31d41 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -71,7 +71,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement <> {entries.map((entry, index: number) => { const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = index % 2 ? RepositoryColorVariant.Light : RepositoryColorVariant.Dark; + const variant = index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; const { icon, color } = getObjectInterfaceDetails(objectType, variant); return ( diff --git a/client/src/theme/colors.ts b/client/src/theme/colors.ts index 03b2fe54b..c5b08039d 100644 --- a/client/src/theme/colors.ts +++ b/client/src/theme/colors.ts @@ -1,4 +1,15 @@ -const Colors = { +import { eSystemObjectType } from '../types/server'; + +type ColorMap = { [key: string]: string }; + +type ColorType = { + defaults: ColorMap; + sidebarOptions: ColorMap; + upload: ColorMap; + repository: { [key in number | string]: ColorMap }; +}; + +const Colors: ColorType = { defaults: { white: '#FFFFFF' }, @@ -13,20 +24,44 @@ const Colors = { upload: { success: '#AFFFA9' }, - // Repository tree view colors [Dark variant, Light Variant] repository: { - default: ['#e9f4fe', '#f4fafe'], - unit: ['#e9f4fe', '#f4fafe'], - project: ['#f6f3fb', '#faf9fd'], - subject: ['#fff9e6', '#fffcf3'], - item: ['#ffe6de', '#ffeee9'], - captureData: ['#edf7ed', '#f6fbf6'] + default: { + dark: '#82b6e0', + regular: '#e9f4fe', + light: '#f4fafe' + }, + [eSystemObjectType.eUnit]: { + dark: '#82b6e0', + regular: '#e9f4fe', + light: '#f4fafe' + }, + [eSystemObjectType.eProject]: { + dark: '#b39ddb', + regular: '#f6f3fb', + light: '#faf9fd' + }, + [eSystemObjectType.eSubject]: { + dark: '#ffe082', + regular: '#fff9e6', + light: '#fffcf3' + }, + [eSystemObjectType.eItem]: { + dark: '#ffab91', + regular: '#ffe6de', + light: '#ffeee9' + }, + [eSystemObjectType.eCaptureData]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + } } }; export enum RepositoryColorVariant { - Dark, - Light + dark = 'dark', + regular = 'regular', + light = 'light' } export default Colors; diff --git a/client/src/theme/index.ts b/client/src/theme/index.ts index 58f6bfc09..d6142e1e5 100644 --- a/client/src/theme/index.ts +++ b/client/src/theme/index.ts @@ -4,7 +4,7 @@ import Colors from './colors'; import { createTypographyOverrides } from './typography'; // https://material-ui.com/customization/palette/ -const palette = { +export const palette = { primary: { light: '#ECF5FD', main: '#0079C4', From 3c271a826f7657ad873a2797e1c2874522dbf1f3 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 30 Sep 2020 14:44:28 +0530 Subject: [PATCH 05/22] fix overlap behavior --- .../RepositoryTreeHeader.tsx | 21 ++++++++++++------- .../RepositoryTreeView/StyledTreeItem.tsx | 18 ++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 14bef2c81..8d9c13ae9 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -7,7 +7,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { display: 'flex', height: 50, - background: palette.primary.light, + backgroundColor: palette.primary.light, borderRadius: 5, width: '100vw', margin: '0px 0px 5px 0px', @@ -15,22 +15,28 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ top: 0, zIndex: 10, [breakpoints.down('lg')]: { - height: 40, + height: 40 } }, treeView: { display: 'flex', flex: 1, position: 'sticky', - left: 20, + left: 0, alignItems: 'center', - marginLeft: 20, color: palette.primary.dark, fontSize: typography.pxToRem(18), fontWeight: typography.fontWeightRegular, [breakpoints.down('lg')]: { - fontSize: typography.pxToRem(15), + fontSize: typography.pxToRem(15) } + }, + treeViewText: { + paddingLeft: 20, + left: 20, + width: '60%', + backgroundColor: palette.primary.light, + zIndex: 10 } })); @@ -40,10 +46,11 @@ function RepositoryTreeHeader(): React.ReactElement { return ( - Tree view + + Tree view + - ); } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 5d9f91dd5..a58b03725 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -37,6 +37,7 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) marginLeft: 5, position: 'sticky', left: 10, + zIndex: 10, [breakpoints.down('lg')]: { width: 15, marginLeft: 8, @@ -62,7 +63,6 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) padding: '5px 10px', [breakpoints.down('lg')]: { fontSize: 12, - padding: '3px 6px', }, backgroundColor: 'transparent !important', '&:hover': { @@ -81,12 +81,13 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) selected: { backgroundColor: 'transparent' } -}))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); +}))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); interface TreeLabelProps { label?: React.ReactNode; metadata: string[]; objectType: number; + color: string; } const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ @@ -97,13 +98,18 @@ const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ position: 'sticky', left: 45, [breakpoints.down('lg')]: { - left: 35, - }, + left: 35 + } + }, + labelText: { + width: '60%', + background: ({ color }: TreeLabelProps) => color, + zIndex: 10 } })); function TreeLabel(props: TreeLabelProps): React.ReactElement { - const classes = useTreeLabelStyles(); + const classes = useTreeLabelStyles(props); const { label, metadata, objectType } = props; const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; @@ -112,7 +118,7 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { - {label} + {label} From 48e715a947743b3d2d1736ce8b013d371ab50262 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 30 Sep 2020 16:22:51 +0530 Subject: [PATCH 06/22] minor fixes --- .../RepositoryTreeView/RepositoryTreeHeader.tsx | 3 +-- .../RepositoryTreeView/StyledTreeItem.tsx | 15 ++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 8d9c13ae9..4a4a9c6a4 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -13,7 +13,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ margin: '0px 0px 5px 0px', position: 'sticky', top: 0, - zIndex: 10, + zIndex: 20, [breakpoints.down('lg')]: { height: 40 } @@ -36,7 +36,6 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ left: 20, width: '60%', backgroundColor: palette.primary.light, - zIndex: 10 } })); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index a58b03725..3b3abee01 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -75,7 +75,6 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) transition: 'all 200ms ease', '&:hover': { transform: 'scale(1.01)', - backgroundColor: ({ color }: StyledTreeItemProps) => fade(color, 0.4), } }, selected: { @@ -104,7 +103,7 @@ const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ labelText: { width: '60%', background: ({ color }: TreeLabelProps) => color, - zIndex: 10 + zIndex: 10, } })); @@ -117,8 +116,10 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { return ( - - {label} + + + {label} + @@ -160,13 +161,13 @@ export function MetadataView(props: MetadataViewProps): React.ReactElement { return ( - + {unit} - + {trimmedMetadataField(subjectId, 20, 10)} - + {trimmedMetadataField(itemName, 15, 5)} From 981a034524c11c98baeaa6c07730565407c56372 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 30 Sep 2020 16:59:15 +0530 Subject: [PATCH 07/22] filter url schemes --- client/src/pages/Repository/index.tsx | 38 +++++++++++++++++---------- client/src/utils/repository.ts | 28 ++++++++++++++++++-- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index c5caab83e..b6523d23b 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import RepositoryFilterView from './components/RepositoryFilterView'; import RepositoryTreeView from './components/RepositoryTreeView'; -import useDebounce from './hooks/useDebounce'; +import { useHistory, useLocation } from 'react-router'; +import { generateRepositoryUrl, parseRepositoryUrl } from '../../utils/repository'; const useStyles = makeStyles(({ breakpoints }) => ({ container: { @@ -25,29 +26,38 @@ export type RepositoryFilter = { function Repository(): React.ReactElement { const classes = useStyles(); + const history = useHistory(); + const { search } = useLocation(); + + const queries = parseRepositoryUrl(search); + const initialFilterState = { units: true, - projects: false, + projects: false }; - const [filter, setFilter] = useState(initialFilterState); - const debouncedFilter = useDebounce(filter, 200); + const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; + + const [filter, setFilter] = useState(defaultFilterState); + + useEffect(() => { + const route = generateRepositoryUrl(filter); + history.push(route); + }, [filter, history]); const onChange = (name: string, value: string | boolean) => { - setFilter(filter => { - return { - ...filter, - [name]: value, - ...(name === 'units' && { projects: false }), - ...(name === 'projects' && { units: false }), - }; - }); + setFilter(filter => ({ + ...filter, + [name]: value, + ...(name === 'units' && { projects: false }), + ...(name === 'projects' && { units: false }), + })); }; return ( - + ); } diff --git a/client/src/utils/repository.ts b/client/src/utils/repository.ts index 00a738ae8..670427856 100644 --- a/client/src/utils/repository.ts +++ b/client/src/utils/repository.ts @@ -1,7 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import lodash from 'lodash'; +import * as qs from 'query-string'; +import { HOME_ROUTES } from '../constants'; import { RepositoryFilter } from '../pages/Repository'; -import { eSystemObjectType } from '../types/server'; import { NavigationResultEntry } from '../types/graphql'; -import lodash from 'lodash'; +import { eSystemObjectType } from '../types/server'; export function getSystemObjectTypesForFilter(filter: RepositoryFilter): eSystemObjectType[] { const objectTypes: eSystemObjectType[] = []; @@ -63,3 +66,24 @@ export function trimmedMetadataField(value: string, start: number, end: number): if (length < 30) return value; return `${value.substring(0, start)}...${value.substring(length - end, length)}`; } + +export function parseRepositoryUrl(search: string): any { + return qs.parse(search, { + parseBooleans: true, + parseNumbers: true, + arrayFormat: 'comma' + }); +} + +export function generateRepositoryUrl(filter: RepositoryFilter): string { + const validate = (value: unknown) => { + if (lodash.isBoolean(value)) { + return value === true; + } + + return true; + }; + + const queryResult = lodash.pickBy(filter, validate); + return `${HOME_ROUTES.REPOSITORY}?${qs.stringify(queryResult)}`; +} From b824fe042f58b5d7ccdfe11ee4758904dd262c86 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 30 Sep 2020 17:09:19 +0530 Subject: [PATCH 08/22] minor fixes --- .../Repository/components/RepositoryTreeView/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 042e31d41..2297956c3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -20,18 +20,19 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ overflow: 'auto', [breakpoints.down('lg')]: { maxHeight: '71vh', - maxWidth: '80vw', - }, + maxWidth: '80vw' + } }, tree: { display: 'flex', flexDirection: 'column', flex: 1, - width: '100vw', + width: '100vw' }, fullView: { display: 'flex', flex: 1, + maxWidth: '83vw', alignItems: 'center', justifyContent: 'center', color: palette.primary.dark From bfef9966481035cb774313607f6efc5779884b78 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 1 Oct 2020 19:10:10 +0530 Subject: [PATCH 09/22] refactor for responsive, cusomizable metadata gridview --- .../components/RepositoryFilterView/index.tsx | 4 +- .../RepositoryTreeHeader.tsx | 17 ++-- .../RepositoryTreeView/RepositoryTreeNode.tsx | 21 +++-- .../RepositoryTreeView/StyledTreeItem.tsx | 78 +++++++++++-------- .../components/RepositoryTreeView/index.tsx | 51 ++++++------ .../pages/Repository/hooks/useRepository.ts | 22 ++++-- client/src/utils/repository.ts | 56 ++++++++++++- 7 files changed, 168 insertions(+), 81 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 1548cda85..d5ae6c197 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -55,8 +55,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ background: palette.background.paper, [breakpoints.down('lg')]: { minWidth: 100, - width: 100, - padding: '5px 10px' + width: 100 }, '&:not(:first-child)': { marginLeft: 10 @@ -69,7 +68,6 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ filterText: { marginLeft: 10, [breakpoints.down('lg')]: { - marginLeft: 5, fontSize: 10 } } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 4a4a9c6a4..d008b9be3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { MetadataView } from './StyledTreeItem'; +import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; +import { eMetadata } from '../../../../types/server'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -9,7 +11,6 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ height: 50, backgroundColor: palette.primary.light, borderRadius: 5, - width: '100vw', margin: '0px 0px 5px 0px', position: 'sticky', top: 0, @@ -38,17 +39,23 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ backgroundColor: palette.primary.light, } })); +interface RepositoryTreeHeaderProps { + metadataColumns: eMetadata[]; +} -function RepositoryTreeHeader(): React.ReactElement { +function RepositoryTreeHeader(props: RepositoryTreeHeaderProps): React.ReactElement { + const { metadataColumns } = props; const classes = useStyles(); - const metadataHeaders = ['Unit', 'SubjectId', 'Item name']; + + const treeColumns = getTreeViewColumns(metadataColumns, true); + const width = getTreeWidth(treeColumns.length); return ( - + Tree view - + ); } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx index e1c8a1329..1e499ec8f 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx @@ -2,12 +2,14 @@ import React from 'react'; import StyledTreeItem from './StyledTreeItem'; import TreeViewContents from './TreeViewContents'; import { eSystemObjectType } from '../../../../types/server'; -import { getRepositoryTreeNodeId, getSortedTreeEntries } from '../../../../utils/repository'; +import { getRepositoryTreeNodeId, getSortedTreeEntries, getTreeColorVariant, getTreeViewColumns } from '../../../../utils/repository'; import { useGetObjectChildren } from '../../hooks/useRepository'; import { AiOutlineFileText } from 'react-icons/ai'; import { Colors } from '../../../../theme'; import { RepositoryColorVariant } from '../../../../theme/colors'; import { RepositoryIcon } from '../../../../components'; +import { TreeViewColumn } from './StyledTreeItem'; +import { RepositoryFilter } from '../..'; interface RepositoryTreeNodeProps { idSystemObject: number; @@ -16,18 +18,19 @@ interface RepositoryTreeNodeProps { color: string; objectType: eSystemObjectType; icon: React.ReactElement; - metadata: string[]; + treeColumns: TreeViewColumn[]; + filter: RepositoryFilter; } function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement { - const { idSystemObject, idObject, name, objectType, icon, color, metadata } = props; + const { idSystemObject, idObject, name, objectType, icon, color, treeColumns, filter } = props; const nodeId = getRepositoryTreeNodeId(idSystemObject, idObject, objectType); - const { getObjectChildren, getObjectChildrenData, getObjectChildrenLoading, getObjectChildrenError } = useGetObjectChildren(idSystemObject); + const { getObjectChildren, getObjectChildrenData, getObjectChildrenLoading, getObjectChildrenError } = useGetObjectChildren(idSystemObject, filter); const isEmpty = !getObjectChildrenData?.getObjectChildren?.entries.length ?? false; - const entries = getSortedTreeEntries(getObjectChildrenData?.getObjectChildren?.entries ?? []); + const metadataColumns = getObjectChildrenData?.getObjectChildren?.metadataColumns ?? []; return ( {entries.map((entry, index: number) => { const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; + const variant = getTreeColorVariant(index); const { icon, color } = getObjectInterfaceDetails(objectType, variant); + const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); return ( ); })} diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 3b3abee01..b60956119 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -3,7 +3,8 @@ import { fade, withStyles, Theme, makeStyles } from '@material-ui/core/styles'; import { TreeItem, TreeItemProps } from '@material-ui/lab'; import React from 'react'; import { animated, useSpring } from 'react-spring'; -import { getTermForSystemObjectType, trimmedMetadataField } from '../../../../utils/repository'; +import { eMetadata } from '../../../../types/server'; +import { computeMetadataViewWidth, getTermForSystemObjectType, trimmedMetadataField } from '../../../../utils/repository'; interface TransitionComponentProps { in?: boolean; @@ -25,10 +26,16 @@ function TransitionComponent(props: TransitionComponentProps): React.ReactElemen ); } +export type TreeViewColumn = { + metadataColumn: eMetadata; + label: string; + size: number; +}; + interface StyledTreeItemProps { color: string; - metadata: string[]; objectType: number; + treeColumns: TreeViewColumn[]; } const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) => ({ @@ -40,20 +47,20 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) zIndex: 10, [breakpoints.down('lg')]: { width: 15, - marginLeft: 8, + marginLeft: 8 }, '& .close': { opacity: 0.3 - }, + } }, root: { - marginTop: 5, + marginTop: 5 }, group: { paddingLeft: 20, borderLeft: `1px dashed ${fade(palette.text.primary, 0.2)}`, [breakpoints.down('lg')]: { - paddingLeft: 15, + paddingLeft: 15 } }, label: { @@ -62,11 +69,11 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) borderRadius: 5, padding: '5px 10px', [breakpoints.down('lg')]: { - fontSize: 12, + fontSize: 12 }, backgroundColor: 'transparent !important', '&:hover': { - backgroundColor: 'transparent', + backgroundColor: 'transparent' } }, content: { @@ -74,19 +81,32 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) borderRadius: 5, transition: 'all 200ms ease', '&:hover': { - transform: 'scale(1.01)', + transform: 'scale(1.01)' } }, selected: { backgroundColor: 'transparent' } -}))((props: TreeItemProps & StyledTreeItemProps) => } TransitionComponent={TransitionComponent} />); +}))((props: TreeItemProps & StyledTreeItemProps) => ( + + } + /> +)); interface TreeLabelProps { label?: React.ReactNode; - metadata: string[]; objectType: number; color: string; + treeColumns: TreeViewColumn[]; } const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ @@ -109,7 +129,7 @@ const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ function TreeLabel(props: TreeLabelProps): React.ReactElement { const classes = useTreeLabelStyles(props); - const { label, metadata, objectType } = props; + const { label, objectType, treeColumns } = props; const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; @@ -122,22 +142,19 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { - + ); } const useMetadataStyles = makeStyles(({ palette, typography, breakpoints }) => ({ metadata: { - display: 'flex', - width: '50vw', - [breakpoints.down('lg')]: { - width: '42vw', - } + display: 'flex' }, column: { display: 'flex', alignItems: 'center', + padding: '0px 10px', fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(18) : undefined, color: ({ header }: MetadataViewProps) => header ? palette.primary.dark : palette.grey[900], fontWeight: ({ header }: MetadataViewProps) => header ? typography.fontWeightRegular : typography.fontWeightLight, @@ -151,25 +168,24 @@ const useMetadataStyles = makeStyles(({ palette, typography, breakpoints }) => ( interface MetadataViewProps { header: boolean; - metadata: string[]; + treeColumns: TreeViewColumn[]; } export function MetadataView(props: MetadataViewProps): React.ReactElement { - const { metadata } = props; + const { treeColumns } = props; const classes = useMetadataStyles(props); - const [unit, subjectId, itemName] = metadata; + + const width = computeMetadataViewWidth(treeColumns); return ( - - - {unit} - - - {trimmedMetadataField(subjectId, 20, 10)} - - - {trimmedMetadataField(itemName, 15, 5)} - + + {treeColumns.map(({ label, size }, index: number) => ( + + + {trimmedMetadataField(label, 20, 10)} + + + ))} ); } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 2297956c3..8da605b3d 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -6,8 +6,7 @@ import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; import RepositoryTreeNode, { getObjectInterfaceDetails } from './RepositoryTreeNode'; import { RepositoryFilter } from '../../index'; import { useGetRootObjects } from '../../hooks/useRepository'; -import { getSystemObjectTypesForFilter, getSortedTreeEntries } from '../../../../utils/repository'; -import { RepositoryColorVariant } from '../../../../theme/colors'; +import { getSystemObjectTypesForFilter, getSortedTreeEntries, getTreeWidth, getTreeColorVariant, getTreeViewColumns } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ @@ -15,24 +14,23 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flex: 5, maxHeight: '72vh', - maxWidth: '83vw', + maxWidth: '83.5vw', flexDirection: 'column', overflow: 'auto', [breakpoints.down('lg')]: { maxHeight: '71vh', - maxWidth: '80vw' + maxWidth: '80.5vw' } }, tree: { display: 'flex', flexDirection: 'column', - flex: 1, - width: '100vw' + flex: 1 }, fullView: { display: 'flex', flex: 1, - maxWidth: '83vw', + maxWidth: '83.5vw', alignItems: 'center', justifyContent: 'center', color: palette.primary.dark @@ -48,21 +46,18 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const classes = useStyles(); const objectTypes = getSystemObjectTypesForFilter(filter); - const { getRootObjectsData, getRootObjectsLoading, getRootObjectsError } = useGetRootObjects(objectTypes); + const { getRootObjectsData, getRootObjectsLoading, getRootObjectsError } = useGetRootObjects(objectTypes, filter); const noFilter = !filter.units && !filter.projects; const entries = getSortedTreeEntries(getRootObjectsData?.getObjectChildren?.entries ?? []); + const metadataColumns = getRootObjectsData?.getObjectChildren?.metadataColumns ?? []; + const width = getTreeWidth(metadataColumns.length); return ( - - } - defaultExpandIcon={} - > - + } defaultExpandIcon={}> + {noFilter && ( Please select a valid filter @@ -72,34 +67,34 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement <> {entries.map((entry, index: number) => { const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; + const variant = getTreeColorVariant(index); const { icon, color } = getObjectInterfaceDetails(objectType, variant); + const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); return ( ); })} - ) - : ( - <> - {!noFilter && ( - - - - )} - - )} - + ) : ( + <> + {!noFilter && ( + + + + )} + + )} ); diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 097fbffee..439570abe 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,6 +1,7 @@ import { eSystemObjectType, eMetadata } from '../../../types/server'; import { useQuery, useLazyQuery, ApolloError } from '@apollo/client'; import { GetObjectChildrenDocument, GetObjectChildrenQuery, GetObjectChildrenQueryVariables } from '../../../types/graphql'; +import { RepositoryFilter } from '..'; interface UseGetRootObjects { getRootObjectsData: GetObjectChildrenQuery | undefined; @@ -8,7 +9,8 @@ interface UseGetRootObjects { getRootObjectsError: ApolloError | undefined; } -function useGetRootObjects(objectTypes: eSystemObjectType[]): UseGetRootObjects { + +function useGetRootObjects(objectTypes: eSystemObjectType[], filter: RepositoryFilter): UseGetRootObjects { const { data: getRootObjectsData, loading: getRootObjectsLoading, error: getRootObjectsError } = useQuery( GetObjectChildrenDocument, { @@ -16,7 +18,7 @@ function useGetRootObjects(objectTypes: eSystemObjectType[]): UseGetRootObjects input: { idRoot: 0, objectTypes, - metadataColumns: [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName] + metadataColumns: getMetadataColumnsForFilter(filter) } } } @@ -36,7 +38,7 @@ interface UseGetObjectChildren { getObjectChildrenError: ApolloError | undefined; } -function useGetObjectChildren(idRoot: number): UseGetObjectChildren { +function useGetObjectChildren(idRoot: number, filter: RepositoryFilter): UseGetObjectChildren { const [getObjectChildren, { data: getObjectChildrenData, loading: getObjectChildrenLoading, error: getObjectChildrenError }] = useLazyQuery( GetObjectChildrenDocument, { @@ -44,7 +46,7 @@ function useGetObjectChildren(idRoot: number): UseGetObjectChildren { input: { idRoot, objectTypes: [], - metadataColumns: [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName] + metadataColumns: getMetadataColumnsForFilter(filter) } } } @@ -54,8 +56,18 @@ function useGetObjectChildren(idRoot: number): UseGetObjectChildren { getObjectChildren, getObjectChildrenData, getObjectChildrenLoading, - getObjectChildrenError + getObjectChildrenError, }; } +function getMetadataColumnsForFilter(filter: RepositoryFilter): eMetadata[] { + const metadataColumns: eMetadata[] = [eMetadata.eSubjectIdentifier, eMetadata.eItemName]; + + if (filter.units || filter.projects) { + metadataColumns.unshift(eMetadata.eUnitAbbreviation); + } + + return metadataColumns; +} + export { useGetRootObjects, useGetObjectChildren }; diff --git a/client/src/utils/repository.ts b/client/src/utils/repository.ts index 670427856..84c433d97 100644 --- a/client/src/utils/repository.ts +++ b/client/src/utils/repository.ts @@ -3,8 +3,10 @@ import lodash from 'lodash'; import * as qs from 'query-string'; import { HOME_ROUTES } from '../constants'; import { RepositoryFilter } from '../pages/Repository'; +import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/StyledTreeItem'; +import { RepositoryColorVariant } from '../theme/colors'; import { NavigationResultEntry } from '../types/graphql'; -import { eSystemObjectType } from '../types/server'; +import { eMetadata, eSystemObjectType } from '../types/server'; export function getSystemObjectTypesForFilter(filter: RepositoryFilter): eSystemObjectType[] { const objectTypes: eSystemObjectType[] = []; @@ -87,3 +89,55 @@ export function generateRepositoryUrl(filter: RepositoryFilter): string { const queryResult = lodash.pickBy(filter, validate); return `${HOME_ROUTES.REPOSITORY}?${qs.stringify(queryResult)}`; } + +export function getTreeWidth(columnSize: number): string { + const width = 50 + columnSize * 10; + if (width <= 80) { + return '83.5vw'; + } + + return `${width}vw`; +} + +export function getTreeColorVariant(index: number): RepositoryColorVariant { + return index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; +} + +export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boolean, values?: string[]): TreeViewColumn[] { + const treeColumns: TreeViewColumn[] = []; + const MIN_SIZE = 10; + + metadataColumns.forEach((metadataColumn, index: number) => { + const treeColumn: TreeViewColumn = { + metadataColumn, + label: values ? values[index] : 'Unknown', + size: MIN_SIZE + }; + + switch (metadataColumn) { + case eMetadata.eUnitAbbreviation: + if (isHeader) treeColumn.label = 'Unit'; + break; + + case eMetadata.eSubjectIdentifier: + if (isHeader) treeColumn.label = 'SubjectId'; + treeColumn.size = MIN_SIZE * 2; + break; + + case eMetadata.eItemName: + if (isHeader) treeColumn.label = 'Item name'; + break; + + default: + break; + } + + treeColumns.push(treeColumn); + }); + + return treeColumns; +} + +export function computeMetadataViewWidth(treeColumns: TreeViewColumn[]): string { + return `${treeColumns.reduce((prev, current) => prev + current.size, 0)}vw`; +} From f4821aa736c12adc7025595cd26733078597d600 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 2 Oct 2020 13:30:03 +0530 Subject: [PATCH 10/22] add zustand state manager --- client/package.json | 3 ++- yarn.lock | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index 492ac4d77..3e3ab7ed9 100644 --- a/client/package.json +++ b/client/package.json @@ -63,7 +63,8 @@ "react-spring": "8.0.27", "react-toastify": "6.0.8", "typescript": "3.9.3", - "yup": "0.29.3" + "yup": "0.29.3", + "zustand": "3.1.3" }, "scripts": { "start": "react-app-rewired start", diff --git a/yarn.lock b/yarn.lock index b7d50cd10..9a0c13141 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17344,3 +17344,8 @@ zen-observable@^0.8.0, zen-observable@^0.8.14: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== + +zustand@3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.1.3.tgz#33df7f56ec11b3003458b60606addc94225b282c" + integrity sha512-Otuzh3r0GsatvsUWeXEwdYjZEw1TItWcAXwDGEHdXE4/k6WbAdVDxC21t/Poq4ZMB+2VaQNKICWu7eCBUJ65tQ== From 2589da29bc9b34d6abe2bf6064b419263814c9a1 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 2 Oct 2020 13:31:25 +0530 Subject: [PATCH 11/22] migrate user context -> zustand state --- client/src/components/shared/Header.tsx | 14 ++-- client/src/components/shared/PrivateRoute.tsx | 6 +- client/src/components/shared/PublicRoute.tsx | 6 +- client/src/context/index.tsx | 12 +--- client/src/index.tsx | 24 +++---- client/src/pages/Login/index.tsx | 18 ++--- client/src/store/index.ts | 3 + client/src/store/user.ts | 65 +++++++++++++++++++ client/src/utils/auth.ts | 20 ------ 9 files changed, 98 insertions(+), 70 deletions(-) create mode 100644 client/src/store/index.ts create mode 100644 client/src/store/user.ts delete mode 100644 client/src/utils/auth.ts diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 5c7fd288c..672f614af 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -1,14 +1,13 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext } from 'react'; +import React from 'react'; import { IoIosLogOut, IoIosNotifications, IoIosSearch } from 'react-icons/io'; import { Link, useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; -import API from '../../api'; -import { ROUTES, HOME_ROUTES, resolveRoute } from '../../constants'; -import { AppContext } from '../../context'; -import { Colors } from '../../theme'; import Logo from '../../assets/images/logo-packrat.square.png'; +import { HOME_ROUTES, resolveRoute, ROUTES } from '../../constants'; +import { useUser } from '../../store'; +import { Colors } from '../../theme'; const useStyles = makeStyles(({ palette, spacing }) => ({ container: { @@ -40,19 +39,18 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ })); function Header(): React.ReactElement { - const { user, updateUser } = useContext(AppContext); const classes = useStyles(); const history = useHistory(); + const { user, logout } = useUser(); const onLogout = async (): Promise => { const isConfirmed = global.confirm('Are you sure you want to logout?'); if (!isConfirmed) return; try { - const { success } = await API.logout(); + const { success } = await logout(); if (success) { - updateUser(null); history.push(ROUTES.LOGIN); } } catch { diff --git a/client/src/components/shared/PrivateRoute.tsx b/client/src/components/shared/PrivateRoute.tsx index 0a5f4bc4d..7353f2b96 100644 --- a/client/src/components/shared/PrivateRoute.tsx +++ b/client/src/components/shared/PrivateRoute.tsx @@ -2,10 +2,10 @@ * PrivateRoute * Renders a route only if the user is authenticated else redirects to login */ -import React, { useContext } from 'react'; +import React from 'react'; import { Route, Redirect, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; -import { AppContext } from '../../context'; +import { useUser } from '../../store'; interface PrivateRouteProps { component?: React.ComponentType; @@ -13,7 +13,7 @@ interface PrivateRouteProps { } function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps & RouteProps): React.ReactElement { - const { user } = useContext(AppContext); + const { user } = useUser(); const render = props => { diff --git a/client/src/components/shared/PublicRoute.tsx b/client/src/components/shared/PublicRoute.tsx index da7ac1c0a..0b9565d58 100644 --- a/client/src/components/shared/PublicRoute.tsx +++ b/client/src/components/shared/PublicRoute.tsx @@ -2,10 +2,10 @@ * PublicRoute * Renders a route based on authentication and restriction specified */ -import React, { useContext } from 'react'; +import React from 'react'; import { Route, Redirect, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; -import { AppContext } from '../../context'; +import { useUser } from '../../store'; interface PublicRouteProps { restricted?: boolean; @@ -13,7 +13,7 @@ interface PublicRouteProps { } function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps & RouteProps): React.ReactElement { - const { user } = useContext(AppContext); + const { user } = useUser(); const render = props => ( !!user && restricted ? : diff --git a/client/src/context/index.tsx b/client/src/context/index.tsx index 82c5d22e0..66a988b1c 100644 --- a/client/src/context/index.tsx +++ b/client/src/context/index.tsx @@ -1,10 +1,7 @@ -import React, { useState, createContext } from 'react'; -import { User } from '../types/graphql'; +import React, { createContext } from 'react'; import useIngestionContext, { Ingestion, IngestionDispatch } from './ingestion'; type AppContextType = { - user: User | null; - updateUser: (user: User | null) => void; ingestion: Ingestion; ingestionDispatch: IngestionDispatch; }; @@ -12,16 +9,9 @@ type AppContextType = { export const AppContext = createContext({} as AppContextType); export const AppContextProvider = ({ children }: { children: React.ReactChild }): React.ReactElement => { - const [user, setUser] = useState(null); const { ingestion, ingestionDispatch } = useIngestionContext(); - const updateUser = (user: User | null) => { - setUser(user); - }; - const value = { - user, - updateUser, ingestion, ingestionDispatch }; diff --git a/client/src/index.tsx b/client/src/index.tsx index bbbf85b8c..1134636d1 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,37 +1,33 @@ import { ApolloProvider } from '@apollo/client'; import { Box, CircularProgress, ThemeProvider } from '@material-ui/core'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { Slide, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; -import { AppContext, AppContextProvider } from './context'; +import { AppContextProvider } from './context'; import './global/root.css'; import { apolloClient } from './graphql'; import { About, Home, Login } from './pages'; import theme from './theme'; -import { getAuthenticatedUser } from './utils/auth'; import { AliveScope } from 'react-activation'; import * as serviceWorker from './serviceWorker'; +import { useUser } from './store'; function AppRouter(): React.ReactElement { const [loading, setLoading] = useState(true); - const { user, updateUser } = useContext(AppContext); + const { initialize } = useUser(); - const initialize = useCallback(async () => { - if (!user) { - const authenticatedUser = await getAuthenticatedUser(); - updateUser(authenticatedUser); - setLoading(false); - } - }, [user, updateUser]); - - useEffect(() => { - initialize(); + const initializeUser = useCallback(async () => { + await initialize(); + setLoading(false); }, [initialize]); + useEffect(() => { + initializeUser(); + }, [initializeUser]); return ( diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index 4e56ab824..8a85233f2 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -1,19 +1,17 @@ import { Box, Container, Typography } from '@material-ui/core'; -import { makeStyles, fade } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import { Field, Formik, FormikHelpers } from 'formik'; import { TextField } from 'formik-material-ui'; -import React, { useContext } from 'react'; +import React from 'react'; import { useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; -import API from '../../api'; +import LoginBackground from '../../assets/images/login-background.png'; import { LoadingButton } from '../../components'; +import Config from '../../config'; import { ROUTES } from '../../constants'; -import { AppContext } from '../../context'; -import { getAuthenticatedUser } from '../../utils/auth'; +import { useUser } from '../../store'; import { actionOnKeyPress } from '../../utils/shared'; import useLoginForm, { ILoginForm } from './hooks/useLoginForm'; -import Config from '../../config'; -import LoginBackground from '../../assets/images/login-background.png'; const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { @@ -92,9 +90,9 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => })); function Login(): React.ReactElement { - const { updateUser } = useContext(AppContext); const classes = useStyles(); const history = useHistory(); + const { login } = useUser(); const { initialValues, loginValidationSchema } = useLoginForm(); @@ -109,12 +107,10 @@ function Login(): React.ReactElement { const { setSubmitting } = actions; try { - const { success, message } = await API.login(email, password); + const { success, message } = await login(email, password); setSubmitting(false); if (success) { - const authenticatedUser = await getAuthenticatedUser(); - updateUser(authenticatedUser); toast.success('Welcome to Packrat'); history.push(ROUTES.HOME); } else { diff --git a/client/src/store/index.ts b/client/src/store/index.ts new file mode 100644 index 000000000..60b7bf19a --- /dev/null +++ b/client/src/store/index.ts @@ -0,0 +1,3 @@ +import useUser from './user'; + +export { useUser }; diff --git a/client/src/store/user.ts b/client/src/store/user.ts new file mode 100644 index 000000000..80eaf3644 --- /dev/null +++ b/client/src/store/user.ts @@ -0,0 +1,65 @@ +import create from 'zustand'; +import { User, GetCurrentUserDocument } from '../types/graphql'; +import { apolloClient } from '../graphql'; +import { QueryOptions } from '@apollo/client'; +import API, { AuthResponseType } from '../api'; + +type UserState = { + user: User | null; + initialize: () => Promise; + login: (email: string, password: string) => Promise; + logout: () => Promise; +}; + +const useUser = create((set, get) => ({ + user: null, + initialize: async () => { + const { user } = get(); + if (!user) { + const user = await getAuthenticatedUser(); + set({ user }); + } + }, + login: async (email: string, password: string): Promise => { + const authResponse = await API.login(email, password); + + if (!authResponse.success) { + return { + ...authResponse, + success: false + }; + } + + const user = await getAuthenticatedUser(); + set({ user }); + + return authResponse; + }, + logout: async (): Promise => { + const authResponse = await API.logout(); + + if (authResponse.success) { + set({ user: null }); + } + + return authResponse; + } +})); + +async function getAuthenticatedUser(): Promise { + const queryOptions: QueryOptions = { + query: GetCurrentUserDocument, + fetchPolicy: 'network-only' + }; + + try { + const { data } = await apolloClient.query(queryOptions); + const { getCurrentUser } = data; + + return getCurrentUser.User; + } catch { + return null; + } +} + +export default useUser; diff --git a/client/src/utils/auth.ts b/client/src/utils/auth.ts deleted file mode 100644 index 46503dc88..000000000 --- a/client/src/utils/auth.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { apolloClient } from '../graphql'; -import { GetCurrentUserDocument } from '../types/graphql'; -import { User } from '../types/graphql'; -import { QueryOptions } from '@apollo/client'; - -export async function getAuthenticatedUser(): Promise { - const queryOptions: QueryOptions = { - query: GetCurrentUserDocument, - fetchPolicy: 'network-only' - }; - - try { - const { data } = await apolloClient.query(queryOptions); - const { getCurrentUser } = data; - - return getCurrentUser.User; - } catch { - return null; - } -} From 5b7551df7c9107cb883c80e31861b300ce4f1448 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 2 Oct 2020 18:28:38 +0530 Subject: [PATCH 12/22] migrate ingestion context -> zustand state --- client/src/context/index.tsx | 28 - client/src/context/ingestion.defaults.ts | 28 - client/src/context/ingestion.ts | 601 ------------------ client/src/index.tsx | 27 +- .../Metadata/Photogrammetry/AssetContents.tsx | 2 +- .../Photogrammetry/IdentifierList.tsx | 2 +- .../Metadata/Photogrammetry/SelectField.tsx | 2 +- .../Metadata/Photogrammetry/index.tsx | 12 +- .../Ingestion/components/Metadata/index.tsx | 14 +- .../components/SubjectItem/ItemList.tsx | 8 +- .../components/SubjectItem/ProjectList.tsx | 8 +- .../components/SubjectItem/SearchList.tsx | 3 +- .../components/SubjectItem/SubjectList.tsx | 3 +- .../SubjectItem/SubjectListItem.tsx | 2 +- .../components/SubjectItem/index.tsx | 16 +- .../Ingestion/components/Uploads/FileList.tsx | 22 +- .../components/Uploads/FileListItem.tsx | 4 +- .../components/Uploads/UploadCompleteList.tsx | 42 +- .../components/Uploads/UploadFilesPicker.tsx | 10 +- .../components/Uploads/UploadList.tsx | 13 +- .../Ingestion/components/Uploads/index.tsx | 14 +- .../pages/Ingestion/hooks/useFilesUpload.ts | 471 -------------- client/src/pages/Ingestion/hooks/useIngest.ts | 49 +- client/src/pages/Ingestion/hooks/useItem.ts | 77 --- .../src/pages/Ingestion/hooks/useMetadata.ts | 221 ------- .../src/pages/Ingestion/hooks/useProject.ts | 71 --- .../src/pages/Ingestion/hooks/useSubject.ts | 134 ---- .../Ingestion/hooks/useVocabularyEntries.ts | 128 ---- client/src/pages/Ingestion/index.tsx | 6 +- client/src/store/index.ts | 11 +- client/src/store/item.ts | 88 +++ client/src/store/metadata.ts | 419 ++++++++++++ client/src/store/project.ts | 70 ++ client/src/store/subject.ts | 121 ++++ client/src/store/upload.ts | 305 +++++++++ client/src/store/user.ts | 6 +- client/src/{context => store}/utils.ts | 5 +- client/src/store/vocabulary.ts | 111 ++++ 38 files changed, 1229 insertions(+), 1925 deletions(-) delete mode 100644 client/src/context/index.tsx delete mode 100644 client/src/context/ingestion.defaults.ts delete mode 100644 client/src/context/ingestion.ts delete mode 100644 client/src/pages/Ingestion/hooks/useFilesUpload.ts delete mode 100644 client/src/pages/Ingestion/hooks/useItem.ts delete mode 100644 client/src/pages/Ingestion/hooks/useMetadata.ts delete mode 100644 client/src/pages/Ingestion/hooks/useProject.ts delete mode 100644 client/src/pages/Ingestion/hooks/useSubject.ts delete mode 100644 client/src/pages/Ingestion/hooks/useVocabularyEntries.ts create mode 100644 client/src/store/item.ts create mode 100644 client/src/store/metadata.ts create mode 100644 client/src/store/project.ts create mode 100644 client/src/store/subject.ts create mode 100644 client/src/store/upload.ts rename client/src/{context => store}/utils.ts (89%) create mode 100644 client/src/store/vocabulary.ts diff --git a/client/src/context/index.tsx b/client/src/context/index.tsx deleted file mode 100644 index 66a988b1c..000000000 --- a/client/src/context/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { createContext } from 'react'; -import useIngestionContext, { Ingestion, IngestionDispatch } from './ingestion'; - -type AppContextType = { - ingestion: Ingestion; - ingestionDispatch: IngestionDispatch; -}; - -export const AppContext = createContext({} as AppContextType); - -export const AppContextProvider = ({ children }: { children: React.ReactChild }): React.ReactElement => { - const { ingestion, ingestionDispatch } = useIngestionContext(); - - const value = { - ingestion, - ingestionDispatch - }; - - return ( - - {children} - - ); -}; - -export * from './ingestion'; -export * from './ingestion.defaults'; -export * from './utils'; diff --git a/client/src/context/ingestion.defaults.ts b/client/src/context/ingestion.defaults.ts deleted file mode 100644 index 5337bab28..000000000 --- a/client/src/context/ingestion.defaults.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { StateItem, PhotogrammetryFields } from './ingestion'; - -export const defaultItem: StateItem = { - id: 'default', - name: '', - entireSubject: false, - selected: true -}; - -export const defaultPhotogrammetryFields: PhotogrammetryFields = { - systemCreated: true, - identifiers: [], - folders: [], - description: '', - dateCaptured: new Date(), - datasetType: null, - datasetFieldId: null, - itemPositionType: null, - itemPositionFieldId: null, - itemArrangementFieldId: null, - focusType: null, - lightsourceType: null, - backgroundRemovalMethod: null, - clusterType: null, - clusterGeometryFieldId: null, - cameraSettingUniform: false, - directory: '' -}; diff --git a/client/src/context/ingestion.ts b/client/src/context/ingestion.ts deleted file mode 100644 index bcbb32a5c..000000000 --- a/client/src/context/ingestion.ts +++ /dev/null @@ -1,601 +0,0 @@ -import { useReducer, Dispatch } from 'react'; -import lodash from 'lodash'; -import { defaultItem } from './ingestion.defaults'; -import { eVocabularySetID } from '../types/server'; -import { Vocabulary } from '../types/graphql'; - -export type FileId = string; - -export enum FileUploadStatus { - READY = 'READY', - UPLOADING = 'UPLOADING', - COMPLETE = 'COMPLETE', - CANCELLED = 'CANCELLED', - FAILED = 'FAILED' -} - -export type IngestionFile = { - id: FileId; - size: number; - name: string; - file: File; - type: number; - status: FileUploadStatus; - progress: number; - selected: boolean; - cancel: (() => void) | null; -}; - -type IngestionUploads = { - files: IngestionFile[]; - loading: boolean; -}; - -export type StateSubject = { - id: number; - arkId: string; - unit: string; - name: string; -}; - -export type SubjectStep = StateSubject[]; - -export type StateProject = { - id: number; - name: string; - selected: boolean; -}; - -export type ProjectStep = StateProject[]; - -export type StateItem = { - id: string; - name: string; - entireSubject: boolean; - selected: boolean; -}; - -export type ItemStep = StateItem[]; - -export type StateIdentifier = { - id: number; - identifier: string; - identifierType: number | null; - selected: boolean; -}; - -export type StateFolder = { - id: number; - name: string; - variantType: number | null; -}; - -export type PhotogrammetryFields = { - systemCreated: boolean; - identifiers: StateIdentifier[]; - folders: StateFolder[]; - description: string; - dateCaptured: Date; - datasetType: number | null; - datasetFieldId: number | null; - itemPositionType: number | null; - itemPositionFieldId: number | null; - itemArrangementFieldId: number | null; - focusType: number | null; - lightsourceType: number | null; - backgroundRemovalMethod: number | null; - clusterType: number | null; - clusterGeometryFieldId: number | null; - cameraSettingUniform: boolean; - directory: string; -}; - -export type StateMetadata = { - photogrammetry: PhotogrammetryFields; - file: IngestionFile; -}; - -export type MetadataStep = StateMetadata[]; -export type VocabularyOption = Pick; -export type StateVocabulary = Map; - -export type Ingestion = { - uploads: IngestionUploads; - subjects: SubjectStep; - projects: ProjectStep; - items: ItemStep; - metadatas: MetadataStep; - vocabularies: StateVocabulary; -}; - -export enum UPLOAD_ACTIONS { - LOAD = 'LOAD', - FETCH_COMPLETE = 'FETCH_COMPLETE', - FETCH_FAILED = 'FETCH_FAILED', - START = 'START', - FAILED = 'FAILED', - PROGRESS = 'PROGRESS', - CANCELLED = 'CANCELLED', - RETRY = 'RETRY', - COMPLETE = 'COMPLETE', - REMOVE = 'REMOVE', - SELECT = 'SELECT', - SET_CANCEL_HANDLER = 'SET_CANCEL_HANDLER', - SET_ASSET_TYPE = 'SET_ASSET_TYPE', - DISCARD_FILES = 'DISCARD_FILES' -} - -export enum SUBJECT_ACTIONS { - ADD_SUBJECT = 'ADD_SUBJECT', - REMOVE_SUBJECT = 'REMOVE_SUBJECT' -} - -export enum PROJECT_ACTIONS { - ADD_PROJECTS = 'ADD_PROJECTS', - UPDATE_PROJECT = 'UPDATE_PROJECT' -} - -export enum ITEM_ACTIONS { - ADD_ITEMS = 'ADD_ITEMS', - UPDATE_ITEM = 'UPDATE_ITEM' -} - -export enum METADATA_ACTIONS { - ADD_METADATA = 'ADD_METADATA', - UPDATE_METADATA_FIELDS = 'UPDATE_METADATA_FIELDS', - INGESTION_RESET = 'INGESTION_RESET', - INGESTION_COMPLETE = 'INGESTION_COMPLETE' -} - -export enum VOCABULARY_ACTIONS { - ADD_VOCABULARIES = 'ADD_VOCABULARIES' -} - -const INGESTION_ACTION = { - UPLOAD: UPLOAD_ACTIONS, - SUBJECT: SUBJECT_ACTIONS, - PROJECT: PROJECT_ACTIONS, - ITEM: ITEM_ACTIONS, - METADATA: METADATA_ACTIONS, - VOCABULARY: VOCABULARY_ACTIONS -}; - -const ingestionState: Ingestion = { - uploads: { - files: [], - loading: true - }, - subjects: [], - projects: [], - items: [defaultItem], - metadatas: [], - vocabularies: new Map() -}; - -type UploadDispatchAction = - | FETCH_COMPLETE - | FETCH_FAILED - | LOAD - | START - | FAILED - | PROGRESS - | CANCELLED - | COMPLETE - | REMOVE - | SELECT - | SET_CANCEL_HANDLER - | SET_ASSET_TYPE - | RETRY - | DISCARD_FILES; - -type FETCH_COMPLETE = { - type: UPLOAD_ACTIONS.FETCH_COMPLETE; - files: IngestionFile[]; -}; - -type FETCH_FAILED = { - type: UPLOAD_ACTIONS.FETCH_FAILED; -}; - -type LOAD = { - type: UPLOAD_ACTIONS.LOAD; - files: IngestionFile[]; -}; - -type START = { - type: UPLOAD_ACTIONS.START; - id: FileId; -}; - -type FAILED = { - type: UPLOAD_ACTIONS.FAILED; - id: FileId; -}; - -type PROGRESS = { - type: UPLOAD_ACTIONS.PROGRESS; - id: FileId; - progress: number; -}; - -type CANCELLED = { - type: UPLOAD_ACTIONS.CANCELLED; - id: FileId; -}; - -type RETRY = { - type: UPLOAD_ACTIONS.RETRY; - id: FileId; -}; - -type COMPLETE = { - type: UPLOAD_ACTIONS.COMPLETE; - id: FileId; -}; - -type REMOVE = { - type: UPLOAD_ACTIONS.REMOVE; - id: FileId; -}; - -type SELECT = { - type: UPLOAD_ACTIONS.SELECT; - id: FileId; - selected: boolean; -}; - -type SET_CANCEL_HANDLER = { - type: UPLOAD_ACTIONS.SET_CANCEL_HANDLER; - id: FileId; - cancel: (() => void) | null; -}; - -type SET_ASSET_TYPE = { - type: UPLOAD_ACTIONS.SET_ASSET_TYPE; - id: FileId; - assetType: number; -}; - -type DISCARD_FILES = { - type: UPLOAD_ACTIONS.DISCARD_FILES; -}; - -type SubjectDispatchAction = ADD_SUBJECT | REMOVE_SUBJECT; - -type ADD_SUBJECT = { - type: SUBJECT_ACTIONS.ADD_SUBJECT; - subject: StateSubject; -}; - -type REMOVE_SUBJECT = { - type: SUBJECT_ACTIONS.REMOVE_SUBJECT; - arkId: string; -}; - -type ProjectDispatchAction = ADD_PROJECTS | UPDATE_PROJECT; - -type ADD_PROJECTS = { - type: PROJECT_ACTIONS.ADD_PROJECTS; - projects: StateProject[]; -}; - -type UPDATE_PROJECT = { - type: PROJECT_ACTIONS.UPDATE_PROJECT; - project: StateProject; -}; - -type ItemDispatchAction = ADD_ITEMS | UPDATE_ITEM; - -type ADD_ITEMS = { - type: ITEM_ACTIONS.ADD_ITEMS; - items: StateItem[]; -}; - -type UPDATE_ITEM = { - type: ITEM_ACTIONS.UPDATE_ITEM; - item: StateItem; -}; - -type MetadataDispatchAction = ADD_METADATA | UPDATE_METADATA_FIELDS | INGESTION_COMPLETE | INGESTION_RESET; - -export type MetadataFieldValue = string | number | boolean | Date; - -type ADD_METADATA = { - type: METADATA_ACTIONS.ADD_METADATA; - metadatas: StateMetadata[]; -}; - -type UPDATE_METADATA_FIELDS = { - type: METADATA_ACTIONS.UPDATE_METADATA_FIELDS; - metadatas: StateMetadata[]; -}; - -type INGESTION_COMPLETE = { - type: METADATA_ACTIONS.INGESTION_COMPLETE; -}; - -type INGESTION_RESET = { - type: METADATA_ACTIONS.INGESTION_RESET; -}; - -type VocabularyDispatchAction = ADD_VOCABULARIES; - -type ADD_VOCABULARIES = { - type: VOCABULARY_ACTIONS.ADD_VOCABULARIES; - vocabularies: StateVocabulary; -}; - -export type IngestionDispatchAction = UploadDispatchAction | SubjectDispatchAction | ProjectDispatchAction | ItemDispatchAction | MetadataDispatchAction | VocabularyDispatchAction; - -const ingestionReducer = (state: Ingestion, action: IngestionDispatchAction): Ingestion => { - const { uploads, subjects, projects, items } = state; - const { files } = uploads; - - switch (action.type) { - case INGESTION_ACTION.UPLOAD.FETCH_COMPLETE: - return { - ...state, - uploads: { - ...uploads, - files: action.files, - loading: false - } - }; - - case INGESTION_ACTION.UPLOAD.FETCH_FAILED: - return { - ...state, - uploads: { - ...uploads, - loading: false - } - }; - - case INGESTION_ACTION.UPLOAD.LOAD: - return { - ...state, - uploads: { - ...uploads, - files: lodash.concat(files, action.files) - } - }; - - case INGESTION_ACTION.UPLOAD.START: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'progress', 0); - lodash.set(file, 'status', FileUploadStatus.UPLOADING); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.FAILED: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'status', FileUploadStatus.FAILED); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.PROGRESS: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'progress', action.progress); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.CANCELLED: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'status', FileUploadStatus.CANCELLED); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.RETRY: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'status', FileUploadStatus.UPLOADING); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.COMPLETE: - return { - ...state, - uploads: { - ...uploads, - files: lodash.filter(files, ({ id }) => id !== action.id) - } - }; - - case INGESTION_ACTION.UPLOAD.REMOVE: - return { - ...state, - uploads: { - ...uploads, - files: files.filter(file => { - return file.id !== action.id; - }) - } - }; - - case INGESTION_ACTION.UPLOAD.SET_CANCEL_HANDLER: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'cancel', action.cancel); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.SELECT: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'selected', action.selected); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.SET_ASSET_TYPE: - return { - ...state, - uploads: { - ...uploads, - files: lodash.forEach(files, file => { - if (file.id === action.id) { - lodash.set(file, 'type', action.assetType); - } - }) - } - }; - - case INGESTION_ACTION.UPLOAD.DISCARD_FILES: - return { - ...state, - uploads: { - ...uploads, - files: lodash.filter(files, ({ selected }) => !selected), - loading: false - } - }; - - case INGESTION_ACTION.METADATA.ADD_METADATA: - return { - ...state, - metadatas: action.metadatas - }; - - case INGESTION_ACTION.METADATA.UPDATE_METADATA_FIELDS: - return { - ...state, - metadatas: action.metadatas - }; - - case INGESTION_ACTION.SUBJECT.ADD_SUBJECT: - return { - ...state, - subjects: [...subjects, action.subject] - }; - - case INGESTION_ACTION.SUBJECT.REMOVE_SUBJECT: - return { - ...state, - subjects: lodash.filter(subjects, ({ arkId }) => arkId !== action.arkId) - }; - - case INGESTION_ACTION.PROJECT.ADD_PROJECTS: - return { - ...state, - projects: action.projects - }; - - case INGESTION_ACTION.PROJECT.UPDATE_PROJECT: - return { - ...state, - projects: lodash.map(projects, project => { - if (project.id === action.project.id) { - return action.project; - } - return project; - }) - }; - - case INGESTION_ACTION.ITEM.ADD_ITEMS: - return { - ...state, - items: action.items - }; - - case INGESTION_ACTION.ITEM.UPDATE_ITEM: - return { - ...state, - items: lodash.map(items, item => { - if (item.id === action.item.id) { - return action.item; - } - return item; - }) - }; - - case INGESTION_ACTION.VOCABULARY.ADD_VOCABULARIES: - return { - ...state, - vocabularies: action.vocabularies - }; - - case INGESTION_ACTION.METADATA.INGESTION_COMPLETE: { - const updatedUploadFiles = [...state.uploads.files].filter(({ selected }) => !selected); - const updatedUploads = { - ...state.uploads, - files: updatedUploadFiles - }; - - return { - ...ingestionState, - uploads: updatedUploads, - vocabularies: state.vocabularies - }; - } - - case INGESTION_ACTION.METADATA.INGESTION_RESET: - return { - ...ingestionState, - vocabularies: state.vocabularies - }; - - default: - return state; - } -}; - -export type IngestionDispatch = Dispatch; - -export interface IngestionReducer { - ingestion: Ingestion; - ingestionDispatch: IngestionDispatch; -} - -export default function useIngestionContext(): IngestionReducer { - const [ingestion, ingestionDispatch] = useReducer(ingestionReducer, ingestionState); - - return { - ingestion, - ingestionDispatch - }; -} diff --git a/client/src/index.tsx b/client/src/index.tsx index 1134636d1..696f7ff24 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -7,7 +7,6 @@ import { Slide, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; -import { AppContextProvider } from './context'; import './global/root.css'; import { apolloClient } from './graphql'; import { About, Home, Login } from './pages'; @@ -55,20 +54,18 @@ function AppRouter(): React.ReactElement { function App(): React.ReactElement { return ( - - - - - - + + + + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index 4cc52a412..fb37cb2c1 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -2,7 +2,7 @@ import { Box, Typography, Select, MenuItem } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../../components'; -import { StateFolder, VocabularyOption } from '../../../../../context'; +import { StateFolder, VocabularyOption } from '../../../../../store'; const useStyles = makeStyles(({ palette, typography }) => ({ header: { diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx index bb8db716e..567278cf4 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { FieldType } from '../../../../../components'; -import { StateIdentifier, VocabularyOption } from '../../../../../context'; +import { StateIdentifier, VocabularyOption } from '../../../../../store'; const useStyles = makeStyles(({ palette, typography, spacing }) => ({ container: { diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx index 750b50654..9327cef48 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx @@ -2,7 +2,7 @@ import { MenuItem, Select } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../../components'; -import { VocabularyOption } from '../../../../../context'; +import { VocabularyOption } from '../../../../../store'; const useStyles = makeStyles(({ palette, typography }) => ({ select: { diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 76a38da20..03878317d 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -1,15 +1,13 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { Box, Checkbox, Typography } from '@material-ui/core'; import { makeStyles, withStyles } from '@material-ui/core/styles'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { FieldType } from '../../../../../components'; import DateFnsUtils from '@date-io/date-fns'; import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers'; import { Colors } from '../../../../../theme'; -import { StateMetadata, defaultPhotogrammetryFields, MetadataFieldValue, PhotogrammetryFields, AppContext, StateIdentifier } from '../../../../../context'; -import useMetadata from '../../../hooks/useMetadata'; +import { StateMetadata, defaultPhotogrammetryFields, MetadataFieldValue, useVocabulary, PhotogrammetryFields, useMetadata, StateIdentifier } from '../../../../../store'; import { eVocabularySetID } from '../../../../../types/server'; -import useVocabularyEntries from '../../../hooks/useVocabularyEntries'; import Description from './Description'; import IdentifierList from './IdentifierList'; import SelectField from './SelectField'; @@ -73,18 +71,16 @@ interface PhotogrammetryProps { function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { const { metadataIndex } = props; - const { ingestion: { metadatas } } = useContext(AppContext); const classes = useStyles(); + const { metadatas, getFieldErrors, updatePhotogrammetryFields } = useMetadata(); const metadata: StateMetadata = metadatas[metadataIndex]; - const { getFieldErrors, updatePhotogrammetryFields } = useMetadata(); - const errors = getFieldErrors(metadata); const [values, setValues] = useState(defaultPhotogrammetryFields); - const { getEntries, getInitialEntry } = useVocabularyEntries(); + const { getEntries, getInitialEntry } = useVocabulary(); useEffect(() => { setValues(metadatas[metadataIndex].photogrammetry); diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index f80e579de..c75a02209 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -1,18 +1,14 @@ import { Box, Breadcrumbs, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import * as qs from 'query-string'; -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { MdNavigateNext } from 'react-icons/md'; import { Redirect, useHistory, useLocation } from 'react-router'; import { toast } from 'react-toastify'; import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { AppContext, FileId, StateItem, StateMetadata, StateProject } from '../../../../context'; -import useItem from '../../hooks/useItem'; -import useMetadata from '../../hooks/useMetadata'; -import useProject from '../../hooks/useProject'; +import { useItem, useMetadata, useProject, useVocabulary, FileId, StateItem, StateMetadata, StateProject } from '../../../../store'; import useIngest from '../../hooks/useIngest'; -import useVocabularyEntries from '../../hooks/useVocabularyEntries'; import Photogrammetry from './Photogrammetry'; const useStyles = makeStyles(({ palette }) => ({ @@ -42,17 +38,15 @@ type QueryParams = { function Metadata(): React.ReactElement { const classes = useStyles(); const { search } = useLocation(); - const { ingestion } = useContext(AppContext); - const { metadatas } = ingestion; const history = useHistory(); const [ingestionLoading, setIngestionLoading] = useState(false); const { getSelectedProject } = useProject(); const { getSelectedItem } = useItem(); - const { getFieldErrors, getMetadataInfo } = useMetadata(); + const { metadatas, getFieldErrors, getMetadataInfo } = useMetadata(); const { ingestPhotogrammetryData, ingestionComplete } = useIngest(); - const { getAssetType } = useVocabularyEntries(); + const { getAssetType } = useVocabulary(); const metadataLength = metadatas.length; const query = qs.parse(search) as QueryParams; diff --git a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index dda31f904..4f23c5c9d 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -1,10 +1,9 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { TableContainer, Table, TableCell, TableHead, TableRow, TableBody, Checkbox } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import { AppContext, StateItem, defaultItem } from '../../../../context'; +import { useItem, StateItem, defaultItem } from '../../../../store'; import { FaRegCircle, FaDotCircle } from 'react-icons/fa'; import { grey, blue } from '@material-ui/core/colors'; -import useItem from '../../hooks/useItem'; import { DebounceInput } from 'react-debounce-input'; const useStyles = makeStyles(({ palette, spacing, typography }) => ({ @@ -52,8 +51,7 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ function ItemList(): React.ReactElement { const classes = useStyles(); - const { ingestion: { items } } = useContext(AppContext); - const { updateItem } = useItem(); + const { items, updateItem } = useItem(); const selectableHeaderStyle = { width: 100 diff --git a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx index 4efd38d0b..e4ccfad4e 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx @@ -1,8 +1,7 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { Select, MenuItem } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import { AppContext } from '../../../../context'; -import useProject from '../../hooks/useProject'; +import { useProject } from '../../../../store'; import lodash from 'lodash'; const useStyles = makeStyles(({ palette }) => ({ @@ -15,8 +14,7 @@ const useStyles = makeStyles(({ palette }) => ({ function ProjectList(): React.ReactElement { const classes = useStyles(); - const { ingestion: { projects } } = useContext(AppContext); - const { getSelectedProject, updateSelectedProject } = useProject(); + const { projects, getSelectedProject, updateSelectedProject } = useProject(); const hasProjects = !projects.length; const selectedProject = getSelectedProject(); diff --git a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx index 2ebebf095..b7882be3c 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx @@ -4,8 +4,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; import { IoIosSearch } from 'react-icons/io'; import { FieldType, LoadingButton } from '../../../../components'; -import { StateSubject } from '../../../../context'; -import { parseSubjectUnitIdentifierToState } from '../../../../context'; +import { StateSubject, parseSubjectUnitIdentifierToState } from '../../../../store'; import { SearchIngestionSubjectsDocument } from '../../../../types/graphql'; import { SubjectUnitIdentifier } from '../../../../types/graphql'; import SubjectList from './SubjectList'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx index eecb88ef0..012d0ffc0 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { Typography, TableContainer, Table, TableCell, TableHead, TableRow, TableBody } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import SubjectListItem from './SubjectListItem'; -import { StateSubject } from '../../../../context'; -import useSubject from '../../hooks/useSubject'; +import { useSubject, StateSubject } from '../../../../store'; const useStyles = makeStyles(({ palette, spacing }) => ({ container: { diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx index 157960596..c1215daf2 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { TableRow, TableCell, Typography, Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { MdAddCircleOutline, MdRemoveCircleOutline } from 'react-icons/md'; -import { StateSubject } from '../../../../context'; +import { StateSubject } from '../../../../store'; const useStyles = makeStyles(() => ({ name: { diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index 638b31eca..aed8c7795 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -1,19 +1,15 @@ import { Box, Chip, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Redirect, useHistory } from 'react-router'; import { toast } from 'react-toastify'; import { FieldType, SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { AppContext } from '../../../../context'; -import useItem from '../../hooks/useItem'; -import useProject from '../../hooks/useProject'; +import { useItem, useMetadata, useProject, useSubject, useVocabulary } from '../../../../store'; import ItemList from './ItemList'; import ProjectList from './ProjectList'; import SearchList from './SearchList'; import SubjectList from './SubjectList'; -import useVocabularyEntries from '../../hooks/useVocabularyEntries'; -import useMetadata from '../../hooks/useMetadata'; const useStyles = makeStyles(({ palette }) => ({ container: { @@ -41,7 +37,7 @@ const useStyles = makeStyles(({ palette }) => ({ function SubjectItem(): React.ReactElement { const classes = useStyles(); const history = useHistory(); - const { ingestion: { metadatas, subjects, projects } } = useContext(AppContext); + const { getSelectedProject } = useProject(); const { getSelectedItem } = useItem(); const [subjectError, setSubjectError] = useState(false); @@ -51,8 +47,10 @@ function SubjectItem(): React.ReactElement { const selectedItem = getSelectedItem(); - const { updateVocabularyEntries } = useVocabularyEntries(); - const { updateMetadataFolders } = useMetadata(); + const { updateVocabularyEntries } = useVocabulary(); + const { subjects } = useSubject(); + const { projects } = useProject(); + const { metadatas, updateMetadataFolders } = useMetadata(); useEffect(() => { if (subjects.length > 0) { diff --git a/client/src/pages/Ingestion/components/Uploads/FileList.tsx b/client/src/pages/Ingestion/components/Uploads/FileList.tsx index be510417f..aa3f3c66f 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileList.tsx @@ -1,8 +1,6 @@ import { AnimatePresence } from 'framer-motion'; -import React, { useContext } from 'react'; -import { AppContext, FileId, IngestionFile, FileUploadStatus, IngestionDispatchAction, UPLOAD_ACTIONS, VocabularyOption } from '../../../../context'; -import useFilesUpload from '../../hooks/useFilesUpload'; -import useVocabularyEntries from '../../hooks/useVocabularyEntries'; +import React from 'react'; +import { useUpload, useVocabulary, FileId, IngestionFile, FileUploadStatus, VocabularyOption } from '../../../../store'; import FileListItem from './FileListItem'; import { eVocabularySetID } from '../../../../types/server'; @@ -11,11 +9,11 @@ interface FileListProps { } function FileList(props: FileListProps): React.ReactElement { - const { ingestionDispatch } = useContext(AppContext); - const { getEntries } = useVocabularyEntries(); + const { selectFile } = useUpload(); + const { getEntries } = useVocabulary(); const { files } = props; - const { changeAssetType, startUpload, retryUpload, cancelUpload, removeUpload } = useFilesUpload(); + const { startUpload, retryUpload, cancelUpload, removeUpload, changeAssetType } = useUpload(); const onChangeType = (id: FileId, assetType: number): void => changeAssetType(id, assetType); @@ -27,15 +25,7 @@ function FileList(props: FileListProps): React.ReactElement { const onRemove = (id: FileId): void => removeUpload(id); - const onSelect = (id: FileId, selected: boolean): void => { - const selectAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.SELECT, - id, - selected - }; - - ingestionDispatch(selectAction); - }; + const onSelect = (id: FileId, selected: boolean): void => selectFile(id, selected); const getFileList = ({ id, name, size, status, selected, progress, type }: IngestionFile, index: number) => { const uploading = status === FileUploadStatus.UPLOADING; diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index 0554811c9..a6e5d7169 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -7,7 +7,7 @@ import { FaRedo, FaRegCircle, FaCheckCircle } from 'react-icons/fa'; import { MdFileUpload } from 'react-icons/md'; import Colors from '../../../../theme/colors'; import { formatBytes } from '../../../../utils/upload'; -import { FileId, VocabularyOption } from '../../../../context'; +import { FileId, VocabularyOption } from '../../../../store'; import { motion } from 'framer-motion'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -165,7 +165,7 @@ function FileListItem(props: FileListItemProps): React.ReactElement { variants={variants} initial='hidden' animate='visible' - whileTap={{ scale: complete ? 0.95 : 1 }} + whileTap={{ scale: complete ? 0.98 : 1 }} > diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index aede4889f..e3251e36f 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -1,13 +1,13 @@ /* eslint-disable react-hooks/exhaustive-deps */ +import { useQuery } from '@apollo/client'; import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { FieldType } from '../../../../components'; -import { AppContext, FileUploadStatus, IngestionDispatchAction, UPLOAD_ACTIONS, parseAssetVersionToState, IngestionFile } from '../../../../context'; +import { parseAssetVersionToState, useUpload } from '../../../../store'; +import { GetUploadedAssetVersionDocument } from '../../../../types/graphql'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; -import { useQuery } from '@apollo/client'; -import { GetUploadedAssetVersionDocument } from '../../../../types/graphql'; const useStyles = makeStyles(({ palette, spacing }) => ({ container: { @@ -49,48 +49,30 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ function UploadListComplete(): React.ReactElement { const classes = useStyles(); - const { ingestion: { uploads }, ingestionDispatch } = useContext(AppContext); - const { files } = uploads; + const { completed, loadCompleted } = useUpload(); const { data, loading, error } = useQuery(GetUploadedAssetVersionDocument); useEffect(() => { if (!loading && !error) { const { getUploadedAssetVersion } = data; const { AssetVersion } = getUploadedAssetVersion; - const fileIds: string[] = uploads.files.map(({ id }) => id); + const fileIds: string[] = completed.map(({ id }) => id); - const files = AssetVersion.map(assetVersion => { + const completedFiles = AssetVersion.map(assetVersion => { const { idAssetVersion } = assetVersion; const id = String(idAssetVersion); if (fileIds.includes(id)) { - return uploads.files.find(file => file.id === id); + return completed.find(file => file.id === id); } return parseAssetVersionToState(assetVersion, assetVersion.Asset.VAssetType); }); - const pendingFiles: IngestionFile[] = uploads.files.filter(({ status }) => status !== FileUploadStatus.COMPLETE); - - const fetchSuccessAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.FETCH_COMPLETE, - files: files.concat(pendingFiles) - }; - - ingestionDispatch(fetchSuccessAction); + loadCompleted(completedFiles); } - - if (!loading && error) { - const fetchFailedAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.FETCH_FAILED, - }; - - ingestionDispatch(fetchFailedAction); - } - }, [data, loading, error, ingestionDispatch]); - - const uploadedFiles = files.filter(({ status }) => status === FileUploadStatus.COMPLETE); + }, [data, loading, error]); return ( @@ -101,8 +83,8 @@ function UploadListComplete(): React.ReactElement { Fetching available files... : <> - {!uploadedFiles.length && No files available} - + {!completed.length && No files available} + } diff --git a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index e8ad2f1de..9e397ff91 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -1,11 +1,10 @@ import { Button, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext } from 'react'; +import React from 'react'; import Dropzone from 'react-dropzone'; import { BsCloudUpload } from 'react-icons/bs'; import { Colors } from '../../../../theme'; -import useFilesUpload from '../../hooks/useFilesUpload'; -import { AppContext } from '../../../../context'; +import { useUpload } from '../../../../store'; const useStyles = makeStyles(({ palette, typography, spacing }) => ({ container: { @@ -37,11 +36,10 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ function UploadFilesPicker(): React.ReactElement { const classes = useStyles(); - const { ingestion: { uploads: { loading } } } = useContext(AppContext); - const { loadFiles } = useFilesUpload(); + const { loading, loadPending } = useUpload(); const onDrop = (acceptedFiles: File[]) => { - loadFiles(acceptedFiles); + loadPending(acceptedFiles); }; return ( diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index c93954cce..0c575c164 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -1,8 +1,8 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext } from 'react'; +import React from 'react'; import { FieldType } from '../../../../components'; -import { AppContext, FileUploadStatus } from '../../../../context'; +import { useUpload } from '../../../../store'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; @@ -46,18 +46,15 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ function UploadList(): React.ReactElement { const classes = useStyles(); - const { ingestion: { uploads } } = useContext(AppContext); - const { files } = uploads; - - const uploadingFiles = [...files].filter(({ status }) => status !== FileUploadStatus.COMPLETE); + const { pending } = useUpload(); return ( - {!uploadingFiles.length && Add files to upload} - + {!pending.length && Add files to upload} + diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 6fa192e73..191a21a26 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import KeepAlive from 'react-activation'; import { SidebarBottomNavigator, Loader } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; @@ -10,10 +10,8 @@ import UploadFilesPicker from './UploadFilesPicker'; import UploadList from './UploadList'; import UploadCompleteList from './UploadCompleteList'; import { useHistory } from 'react-router'; -import useFilesUpload from '../../hooks/useFilesUpload'; import { toast } from 'react-toastify'; -import { AppContext } from '../../../../context'; -import useVocabularyEntries from '../../hooks/useVocabularyEntries'; +import { useVocabulary, useUpload, useMetadata } from '../../../../store'; const useStyles = makeStyles(({ palette, typography, spacing }) => ({ container: { @@ -59,9 +57,9 @@ function Uploads(): React.ReactElement { const [loadingVocabulary, setLoadingVocabulary] = useState(true); const [gettingAssetDetails, setGettingAssetDetails] = useState(false); const [discardingFiles, setDiscardingFiles] = useState(false); - const { ingestion: { uploads } } = useContext(AppContext); - const { updateMetadataSteps, discardFiles } = useFilesUpload(); - const { updateVocabularyEntries } = useVocabularyEntries(); + const { completed, discardFiles } = useUpload(); + const { updateMetadataSteps } = useMetadata(); + const { updateVocabularyEntries } = useVocabulary(); const fetchVocabularyEntries = async () => { setLoadingVocabulary(true); @@ -98,7 +96,7 @@ function Uploads(): React.ReactElement { }; const onDiscard = async () => { - if (uploads.files.length) { + if (completed.length) { try { setDiscardingFiles(true); await discardFiles(); diff --git a/client/src/pages/Ingestion/hooks/useFilesUpload.ts b/client/src/pages/Ingestion/hooks/useFilesUpload.ts deleted file mode 100644 index 1f004676d..000000000 --- a/client/src/pages/Ingestion/hooks/useFilesUpload.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { ApolloQueryResult, FetchResult } from '@apollo/client'; -import lodash from 'lodash'; -import { useCallback, useContext } from 'react'; -import { toast } from 'react-toastify'; -import { - AppContext, - defaultPhotogrammetryFields, - FileId, - FileUploadStatus, - IngestionDispatchAction, - IngestionFile, - METADATA_ACTIONS, - parseFileId, - parseItemToState, - parseProjectToState, - parseSubjectUnitIdentifierToState, - StateIdentifier, - StateItem, - StateMetadata, - StateProject, - StateSubject, - UPLOAD_ACTIONS -} from '../../../context'; -import { apolloClient, apolloUploader } from '../../../graphql'; -import { - DiscardUploadedAssetVersionsDocument, - DiscardUploadedAssetVersionsMutation, - GetAssetVersionsDetailsDocument, - GetAssetVersionsDetailsQuery, - Project, - UploadAssetDocument, - UploadAssetMutation, - UploadStatus -} from '../../../types/graphql'; -import { eVocabularySetID } from '../../../types/server'; -import useItem from './useItem'; -import useMetadata from './useMetadata'; -import useProject from './useProject'; -import useSubject from './useSubject'; -import useVocabularyEntries from './useVocabularyEntries'; -import { generateFileId } from '../../../utils/upload'; - -type MetadataUpdate = { - valid: boolean; - selectedFiles: boolean; -}; - -interface UseFilesUpload { - updateMetadataSteps: () => Promise; - loadFiles: (acceptedFiles: File[]) => void; - startUpload: (id: FileId) => void; - cancelUpload: (id: FileId) => void; - retryUpload: (id: FileId) => void; - removeUpload: (id: FileId) => void; - changeAssetType: (id: FileId, type: number) => void; - discardFiles: () => Promise; -} - -const useFilesUpload = (): UseFilesUpload => { - const { ingestion, ingestionDispatch } = useContext(AppContext); - const { files } = ingestion.uploads; - - const { getInitialEntry } = useVocabularyEntries(); - const { addSubjects } = useSubject(); - const { addProjects } = useProject(); - const { addItems } = useItem(); - const { getStateFolders } = useMetadata(); - - const getFile = useCallback((id: FileId): IngestionFile | undefined => lodash.find(files, { id }), [files]); - - const getSelectedFiles = useCallback((): IngestionFile[] => files.filter(({ selected }) => selected), [files]); - - const updateMetadataSteps = async (): Promise => { - const selectedFiles = getSelectedFiles(); - - if (!selectedFiles.length) { - return { - valid: false, - selectedFiles: false - }; - } - - const idAssetVersions: number[] = lodash.map(selectedFiles, ({ id }) => parseFileId(id)); - - const defaultIdentifier: StateIdentifier = { - id: 0, - identifier: '', - identifierType: getInitialEntry(eVocabularySetID.eIdentifierIdentifierType), - selected: false - }; - - const defaultVocabularyFields = { - ...defaultPhotogrammetryFields, - datasetType: getInitialEntry(eVocabularySetID.eCaptureDataDatasetType), - identifiers: [defaultIdentifier] - }; - - try { - const assetVersionDetailsQuery: ApolloQueryResult = await apolloClient.query({ - query: GetAssetVersionsDetailsDocument, - variables: { - input: { - idAssetVersions - } - } - }); - - const { data } = assetVersionDetailsQuery; - - if (data) { - const { - getAssetVersionsDetails: { Details } - } = data; - - const subjects: StateSubject[] = []; - const projects: StateProject[] = []; - const items: StateItem[] = []; - const metadatas: StateMetadata[] = []; - - for (let index = 0; index < Details.length; index++) { - const { idAssetVersion, SubjectUnitIdentifier: foundSubjectUnitIdentifier, Project: foundProject, Item: foundItem, CaptureDataPhoto } = Details[index]; - - if (foundSubjectUnitIdentifier) { - const subject: StateSubject = parseSubjectUnitIdentifierToState(foundSubjectUnitIdentifier); - subjects.push(subject); - } - - if (foundProject) { - const stateProjects: StateProject[] = foundProject.map((project: Project, index: number) => parseProjectToState(project, !index)); - projects.push(...stateProjects); - } - - if (foundItem) { - const item: StateItem = parseItemToState(foundItem, !index, index); - items.push(item); - } - - let metadataStep: StateMetadata; - const file = files.find((file: IngestionFile) => parseFileId(file.id) === idAssetVersion); - - if (!file) { - toast.error('Ingestion file not found'); - throw new Error(); - } - - if (CaptureDataPhoto) { - const { identifiers, folders } = CaptureDataPhoto; - const parsedIdentifiers: StateIdentifier[] = identifiers.map( - ({ identifier, identifierType }, index): StateIdentifier => ({ - id: index, - identifier, - identifierType, - selected: true - }) - ); - - const stateIdentifiers = parsedIdentifiers.length ? parsedIdentifiers : defaultVocabularyFields.identifiers; - - metadataStep = { - file, - photogrammetry: { - ...defaultVocabularyFields, - ...(CaptureDataPhoto && { - ...CaptureDataPhoto, - dateCaptured: new Date(CaptureDataPhoto.dateCaptured), - folders: getStateFolders(folders), - identifiers: stateIdentifiers - }) - } - }; - - metadatas.push(metadataStep); - } else { - metadataStep = { - file, - photogrammetry: { - ...defaultVocabularyFields - } - }; - metadatas.push(metadataStep); - } - } - - addSubjects(lodash.uniqBy(subjects, 'arkId')); - addProjects(projects); - addItems(lodash.uniqBy(items, 'name')); - - const addMetadataStepAction: IngestionDispatchAction = { - type: METADATA_ACTIONS.ADD_METADATA, - metadatas - }; - - ingestionDispatch(addMetadataStepAction); - - return { - valid: true, - selectedFiles: true - }; - } - } catch { - toast.error('Failed to ingest selected files, please try again later'); - } - - return { - valid: false, - selectedFiles: true - }; - }; - - const loadFiles = useCallback( - (acceptedFiles: File[]) => { - if (acceptedFiles.length) { - const ingestionFiles: IngestionFile[] = []; - acceptedFiles.forEach((file: File): void => { - const id = generateFileId(); - const alreadyContains = !!lodash.find(files, { id }); - - const { name, size } = file; - - const type = getInitialEntry(eVocabularySetID.eAssetAssetType); - - if (!type) { - toast.error(`Vocabulary for file ${name} not found`); - return; - } - - if (!alreadyContains) { - const ingestionFile = { - id, - file, - name, - size, - status: FileUploadStatus.READY, - progress: 0, - type, - selected: false, - cancel: null - }; - - ingestionFiles.push(ingestionFile); - } else { - toast.info(`${file.name} was already loaded`); - } - }); - - const loadAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.LOAD, - files: ingestionFiles - }; - - ingestionDispatch(loadAction); - } - }, - [files, getInitialEntry, ingestionDispatch] - ); - - const changeAssetType = useCallback( - (id: FileId, assetType: number): void => { - const changeAssetTypeAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.SET_ASSET_TYPE, - id, - assetType - }; - - ingestionDispatch(changeAssetTypeAction); - }, - [ingestionDispatch] - ); - - const startUpload = (id: FileId) => { - const startAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.START, - id - }; - - const file = getFile(id); - - if (file) { - ingestionDispatch(startAction); - startUploadTransfer(file); - } - }; - - const retryUpload = (id: FileId): void => { - const retryAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.RETRY, - id - }; - - const file = getFile(id); - - if (file) { - ingestionDispatch(retryAction); - startUploadTransfer(file); - } - }; - - const cancelUpload = useCallback( - (id: FileId): void => { - const file = getFile(id); - - if (file) { - if (file.status === 'UPLOADING') { - const { cancel } = file; - if (cancel) { - cancel(); - const cancelledAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.CANCELLED, - id - }; - - ingestionDispatch(cancelledAction); - toast.warn('Upload has been cancelled'); - } - } - } - }, - [getFile, ingestionDispatch] - ); - - const removeUpload = useCallback( - (id: FileId): void => { - const removeAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.REMOVE, - id - }; - - ingestionDispatch(removeAction); - }, - [ingestionDispatch] - ); - - const startUploadTransfer = useCallback( - async (ingestionFile: IngestionFile) => { - const { id, file, type } = ingestionFile; - - const completeAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.COMPLETE, - id - }; - - const errorAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.FAILED, - id - }; - - try { - const onProgress = (event: ProgressEvent) => { - const { loaded, total } = event; - const progress = Math.floor((loaded / total) * 100); - const updateProgress = !(progress % 5); - - if (updateProgress) { - const uploadProgressAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.PROGRESS, - id, - progress - }; - - ingestionDispatch(uploadProgressAction); - } - }; - - const onCancel = (cancel: () => void) => { - const setCancel: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.SET_CANCEL_HANDLER, - id, - cancel - }; - - ingestionDispatch(setCancel); - }; - - const { data } = await apolloUploader({ - mutation: UploadAssetDocument, - variables: { file, type }, - refetchQueries: ['getUploadedAssetVersion'], - useUpload: true, - onProgress, - onCancel - }); - - const { uploadAsset }: UploadAssetMutation = data; - - if (uploadAsset) { - const { status, error } = uploadAsset; - - if (status === UploadStatus.Complete) { - ingestionDispatch(completeAction); - toast.success(`Upload finished for ${file.name}`); - } else if (status === UploadStatus.Failed) { - const errorMessage = error || `Upload failed for ${file.name}`; - toast.error(errorMessage); - ingestionDispatch(errorAction); - } - } - } catch ({ message }) { - const file = getFile(id); - if (file) { - if (file.status !== FileUploadStatus.CANCELLED) { - toast.error(message); - ingestionDispatch(errorAction); - } - } - } - }, - [getFile, ingestionDispatch] - ); - - const discardFiles = async (): Promise => { - const selectedFiles = getSelectedFiles(); - - if (!selectedFiles.length) { - toast.warn('Please select at least 1 file to discard'); - return; - } - - const isConfirmed = global.confirm('Do you want to discard selected items?'); - - if (!isConfirmed) return; - - const idAssetVersions: number[] = selectedFiles.map(({ id }) => parseFileId(id)); - - const discardMutationVariables = { - input: { - idAssetVersions - } - }; - - try { - const { data }: FetchResult = await apolloClient.mutate({ - mutation: DiscardUploadedAssetVersionsDocument, - variables: discardMutationVariables - }); - - if (data) { - const { discardUploadedAssetVersions } = data; - const { success } = discardUploadedAssetVersions; - - if (!success) { - toast.error('Failed to discard selected files'); - return; - } - - const discardFilesAction: IngestionDispatchAction = { - type: UPLOAD_ACTIONS.DISCARD_FILES - }; - - ingestionDispatch(discardFilesAction); - - toast.info('Selected files have been discarded'); - return; - } - } catch { - toast.error('Failed to discard selected files'); - } - }; - - return { - updateMetadataSteps, - loadFiles, - startUpload, - cancelUpload, - retryUpload, - removeUpload, - changeAssetType, - discardFiles - }; -}; - -export default useFilesUpload; diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index e1e72ad26..127f4b88f 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -1,8 +1,4 @@ -import { useContext } from 'react'; -import { AppContext, StateItem, StateProject, StateIdentifier, defaultItem, IngestionDispatchAction, METADATA_ACTIONS, parseFileId, isNewItem } from '../../../context'; -import useItem from './useItem'; -import useProject from './useProject'; -import useMetadata from './useMetadata'; +import { useItem, useProject, useMetadata, useVocabulary, useSubject, useUpload, defaultItem, StateIdentifier, StateItem, StateProject } from '../../../store'; import { IngestDataMutation, IngestIdentifierInput, IngestFolderInput, IngestPhotogrammetryInput, IngestDataDocument, IngestSubjectInput } from '../../../types/graphql'; import { apolloClient } from '../../../graphql'; import lodash from 'lodash'; @@ -10,7 +6,7 @@ import { FetchResult } from '@apollo/client'; import { toast } from 'react-toastify'; import { useHistory } from 'react-router'; import { HOME_ROUTES, resolveSubRoute, INGESTION_ROUTES_TYPE } from '../../../constants/routes'; -import useVocabularyEntries from './useVocabularyEntries'; +import { isNewItem, parseFileId } from '../../../store/utils'; interface UseIngest { ingestPhotogrammetryData: () => Promise; @@ -19,17 +15,20 @@ interface UseIngest { } function useIngest(): UseIngest { - const { - ingestion: { subjects, metadatas }, - ingestionDispatch - } = useContext(AppContext); - const history = useHistory(); + const { removeSelectedUploads } = useUpload(); + const { subjects } = useSubject(); const { getSelectedProject } = useProject(); const { getSelectedItem } = useItem(); - const { getSelectedIdentifiers } = useMetadata(); - const { getAssetType } = useVocabularyEntries(); + const { metadatas, getSelectedIdentifiers } = useMetadata(); + const { getAssetType } = useVocabulary(); + + const resetUploads = useUpload(state => state.reset); + const resetSubjects = useSubject(state => state.reset); + const resetProjects = useProject(state => state.reset); + const resetItems = useItem(state => state.reset); + const resetMetadatas = useMetadata(state => state.reset); const ingestPhotogrammetryData = async (): Promise => { try { @@ -62,7 +61,7 @@ function useIngest(): UseIngest { let ingestItemId: number | null = null; - if (!isDefaultItem || !isNewItem(id)) { + if (!isDefaultItem || isNewItem(id)) { ingestItemId = Number.parseInt(id, 10); } @@ -188,23 +187,23 @@ function useIngest(): UseIngest { return false; }; - const ingestionComplete = (): void => { - const ingestionCompleteAction: IngestionDispatchAction = { - type: METADATA_ACTIONS.INGESTION_COMPLETE - }; - - ingestionDispatch(ingestionCompleteAction); + const resetIngestionState = () => { + resetSubjects(); + resetProjects(); + resetItems(); + resetMetadatas(); + }; + const ingestionComplete = (): void => { + removeSelectedUploads(); + resetIngestionState(); const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, INGESTION_ROUTES_TYPE.UPLOADS); history.push(nextRoute); }; const ingestionReset = (): void => { - const ingestionResetAction: IngestionDispatchAction = { - type: METADATA_ACTIONS.INGESTION_RESET - }; - - ingestionDispatch(ingestionResetAction); + resetUploads(); + resetIngestionState(); }; return { diff --git a/client/src/pages/Ingestion/hooks/useItem.ts b/client/src/pages/Ingestion/hooks/useItem.ts deleted file mode 100644 index aaf416f94..000000000 --- a/client/src/pages/Ingestion/hooks/useItem.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { StateItem, AppContext, IngestionDispatchAction, ITEM_ACTIONS, defaultItem } from '../../../context'; -import { useContext } from 'react'; -import lodash from 'lodash'; - -interface UseItem { - getSelectedItem: () => StateItem | undefined; - addItems: (items: StateItem[]) => void; - updateItem: (item: StateItem) => void; -} - -function useItem(): UseItem { - const { - ingestion: { items }, - ingestionDispatch - } = useContext(AppContext); - - const getSelectedItem = () => lodash.find(items, { selected: true }); - - const addItems = (fetchedItems: StateItem[]): void => { - const currentDefaultItem = lodash.find(items, { id: defaultItem.id }); - - if (currentDefaultItem) { - const addItemsAction = (items: StateItem[]): IngestionDispatchAction => ({ - type: ITEM_ACTIONS.ADD_ITEMS, - items - }); - - if (!fetchedItems.length) { - const selectedDefaultItem = { ...currentDefaultItem, selected: true }; - ingestionDispatch(addItemsAction([selectedDefaultItem])); - return; - } - - const newItemSelected = lodash.find(fetchedItems, { selected: true }); - - if (newItemSelected) { - currentDefaultItem.selected = false; - } - - const newItems: StateItem[] = [currentDefaultItem].concat(fetchedItems); - - ingestionDispatch(addItemsAction(newItems)); - } - }; - - const updateItem = (item: StateItem) => { - const { selected } = item; - - const updateItemAction = (item: StateItem): IngestionDispatchAction => ({ - type: ITEM_ACTIONS.UPDATE_ITEM, - item - }); - - if (selected) { - const alreadySelected: StateItem | undefined = getSelectedItem(); - - if (alreadySelected) { - const unselectedItem = { - ...alreadySelected, - selected: false - }; - - ingestionDispatch(updateItemAction(unselectedItem)); - } - } - - ingestionDispatch(updateItemAction(item)); - }; - - return { - addItems, - updateItem, - getSelectedItem - }; -} - -export default useItem; diff --git a/client/src/pages/Ingestion/hooks/useMetadata.ts b/client/src/pages/Ingestion/hooks/useMetadata.ts deleted file mode 100644 index de0dd6d6d..000000000 --- a/client/src/pages/Ingestion/hooks/useMetadata.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { useContext } from 'react'; -import { AppContext, FileId, StateMetadata, PhotogrammetryFields, METADATA_ACTIONS, IngestionDispatchAction, StateFolder, StateIdentifier, parseFileId } from '../../../context'; -import lodash from 'lodash'; -import { GetContentsForAssetVersionsDocument, AreCameraSettingsUniformDocument, IngestFolder, AssetVersionContent } from '../../../types/graphql'; -import { apolloClient } from '../../../graphql'; -import { eVocabularySetID } from '../../../types/server'; -import useVocabularyEntries from './useVocabularyEntries'; -import { toast } from 'react-toastify'; - -type MetadataInfo = { - metadata: StateMetadata; - metadataIndex: number; - isLast: boolean; -}; - -type FieldErrors = { - photogrammetry: { - dateCaptured: boolean; - datasetType: boolean; - }; -}; - -interface UseMetadata { - getStateFolders: (folders: IngestFolder[]) => StateFolder[]; - getSelectedIdentifiers: (metadata: StateMetadata) => StateIdentifier[] | undefined; - getFieldErrors: (metadata: StateMetadata) => FieldErrors; - getCurrentMetadata: (id: FileId) => StateMetadata | undefined; - getMetadataInfo: (id: FileId) => MetadataInfo; - updatePhotogrammetryFields: (metadataIndex: number, values: PhotogrammetryFields) => void; - updateMetadataFolders: () => Promise; -} - -function useMetadata(): UseMetadata { - const { - ingestion: { metadatas }, - ingestionDispatch - } = useContext(AppContext); - - const { getAssetType, getInitialEntry } = useVocabularyEntries(); - - const idAssetVersions: number[] = [...metadatas].map(({ file: { id } }) => parseFileId(id)); - - const getSelectedIdentifiers = (metadata: StateMetadata): StateIdentifier[] | undefined => lodash.filter(metadata.photogrammetry.identifiers, { selected: true }); - - const getFieldErrors = (metadata: StateMetadata): FieldErrors => { - const errors: FieldErrors = { - photogrammetry: { - dateCaptured: false, - datasetType: false - } - }; - - const { file } = metadata; - const { type } = file; - - const assetType = getAssetType(type); - - if (assetType.photogrammetry) { - errors.photogrammetry.dateCaptured = metadata.photogrammetry.dateCaptured.toString() === 'Invalid Date'; - errors.photogrammetry.datasetType = metadata.photogrammetry.datasetType === null; - } - - return errors; - }; - - const getCurrentMetadata = (id: FileId): StateMetadata | undefined => metadatas.find(({ file }) => file.id === id); - - const getMetadataInfo = (id: FileId): MetadataInfo => { - const metadataLength = metadatas.length; - const metadata: StateMetadata | undefined = metadatas.find(({ file }) => file.id === id); - const metadataIndex = lodash.indexOf(metadatas, metadata); - const isLast = metadataIndex + 1 === metadataLength; - - return { - metadata: metadatas[metadataIndex], - metadataIndex, - isLast - }; - }; - - const updatePhotogrammetryFields = (metadataIndex: number, values: PhotogrammetryFields): void => { - const updatedMetadatas = lodash.map([...metadatas], (metadata: StateMetadata, index: number) => { - if (index === metadataIndex) { - return { - ...metadata, - photogrammetry: { - ...values - } - }; - } - - return metadata; - }); - - const updateMetadataFieldsAction: IngestionDispatchAction = { - type: METADATA_ACTIONS.UPDATE_METADATA_FIELDS, - metadatas: updatedMetadatas - }; - - ingestionDispatch(updateMetadataFieldsAction); - }; - - const getStateFolders = (folders: IngestFolder[]): StateFolder[] => { - const stateFolders: StateFolder[] = folders.map(({ name, variantType }, index: number) => ({ - id: index, - name, - variantType - })); - - return stateFolders; - }; - - const getInitialStateFolders = (folders: string[]): StateFolder[] => { - const stateFolders: StateFolder[] = folders.map((folder, index: number) => ({ - id: index, - name: folder, - variantType: getInitialEntry(eVocabularySetID.eCaptureDataFileVariantType) - })); - - return stateFolders; - }; - - const updateMetadataFolders = async (): Promise => { - const variables = { - input: { - idAssetVersions - } - }; - - const { data } = await apolloClient.query({ - query: GetContentsForAssetVersionsDocument, - variables - }); - - const { getContentsForAssetVersions } = data; - - const { AssetVersionContent: foundAssetVersionContent } = getContentsForAssetVersions; - - let updatedMetadatas = await updateCameraSettings(metadatas); - - foundAssetVersionContent.forEach(({ idAssetVersion, folders }: AssetVersionContent) => { - updatedMetadatas = updatedMetadatas.map(metadata => { - const { file, photogrammetry } = metadata; - const fileId = parseFileId(file.id); - - if (fileId === idAssetVersion) { - if (photogrammetry.folders.length) { - return metadata; - } - - const stateFolders: StateFolder[] = getInitialStateFolders(folders); - - return { - ...metadata, - photogrammetry: { - ...photogrammetry, - folders: stateFolders - } - }; - } - - return metadata; - }); - }); - - const updateMetadataFieldsAction: IngestionDispatchAction = { - type: METADATA_ACTIONS.UPDATE_METADATA_FIELDS, - metadatas: updatedMetadatas - }; - - ingestionDispatch(updateMetadataFieldsAction); - }; - - const updateCameraSettings = async (metadatas: StateMetadata[]): Promise => { - const updatedMetadatas = metadatas.slice(); - - for (let index = 0; index < updatedMetadatas.length; index++) { - const metadata = updatedMetadatas[index]; - const { file, photogrammetry } = metadata; - const idAssetVersion = parseFileId(file.id); - - const assetType = getAssetType(file.type); - - if (assetType.photogrammetry) { - const variables = { - input: { - idAssetVersion - } - }; - - try { - const { data } = await apolloClient.query({ - query: AreCameraSettingsUniformDocument, - variables - }); - - const { areCameraSettingsUniform } = data; - const { isUniform } = areCameraSettingsUniform; - - photogrammetry.cameraSettingUniform = isUniform; - } catch { - toast.error('Failed to retrieve camera settings details'); - } - } - } - - return updatedMetadatas; - }; - - return { - getStateFolders, - getSelectedIdentifiers, - getFieldErrors, - getCurrentMetadata, - getMetadataInfo, - updatePhotogrammetryFields, - updateMetadataFolders - }; -} - -export default useMetadata; diff --git a/client/src/pages/Ingestion/hooks/useProject.ts b/client/src/pages/Ingestion/hooks/useProject.ts deleted file mode 100644 index f36c5bbc8..000000000 --- a/client/src/pages/Ingestion/hooks/useProject.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { StateProject, AppContext, IngestionDispatchAction, PROJECT_ACTIONS } from '../../../context'; -import { useContext } from 'react'; -import lodash from 'lodash'; - -interface UseProject { - getSelectedProject: () => StateProject | undefined; - addProjects: (projects: StateProject[]) => void; - updateSelectedProject: (id: number) => void; - updateProject: (project: StateProject) => void; -} - -function useProject(): UseProject { - const { - ingestion: { projects }, - ingestionDispatch - } = useContext(AppContext); - - const getSelectedProject = () => lodash.find(projects, { selected: true }); - - const addProjects = (fetchedProjects: StateProject[]) => { - if (!fetchedProjects.length) return; - - const addItemsAction: IngestionDispatchAction = { - type: PROJECT_ACTIONS.ADD_PROJECTS, - projects: fetchedProjects - }; - - ingestionDispatch(addItemsAction); - }; - - const updateSelectedProject = (id: number): void => { - const project: StateProject | undefined = lodash.find(projects, { id }); - - if (project) { - const { selected } = project; - if (!selected) { - const alreadySelected: StateProject | undefined = getSelectedProject(); - - if (alreadySelected) { - const unselectedProject = { - ...alreadySelected, - selected: false - }; - - updateProject(unselectedProject); - } - project.selected = true; - } - - updateProject(project); - } - }; - - const updateProject = (project: StateProject): void => { - const updateProjectAction: IngestionDispatchAction = { - type: PROJECT_ACTIONS.UPDATE_PROJECT, - project - }; - - ingestionDispatch(updateProjectAction); - }; - - return { - addProjects, - updateProject, - updateSelectedProject, - getSelectedProject - }; -} - -export default useProject; diff --git a/client/src/pages/Ingestion/hooks/useSubject.ts b/client/src/pages/Ingestion/hooks/useSubject.ts deleted file mode 100644 index d61aeebc0..000000000 --- a/client/src/pages/Ingestion/hooks/useSubject.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { StateSubject, AppContext, IngestionDispatchAction, SUBJECT_ACTIONS, StateItem, StateProject } from '../../../context'; -import { parseItemToState, parseProjectToState } from '../../../context'; -import { useContext } from 'react'; -import lodash from 'lodash'; -import { toast } from 'react-toastify'; -import useItem from './useItem'; -import useProject from './useProject'; -import { apolloClient } from '../../../graphql'; -import { - GetIngestionItemsForSubjectsDocument, - GetIngestionProjectsForSubjectsDocument, - GetIngestionItemsForSubjectsQuery, - GetIngestionProjectsForSubjectsQuery -} from '../../../types/graphql'; -import { Item, Project } from '../../../types/graphql'; -import { ApolloQueryResult } from '@apollo/client'; - -interface UseSubject { - addSubject: (subject: StateSubject) => Promise; - addSubjects: (subjects: StateSubject[]) => Promise; - removeSubject: (arkId: string) => void; -} - -function useSubject(): UseSubject { - const { - ingestion: { subjects }, - ingestionDispatch - } = useContext(AppContext); - - const { addItems } = useItem(); - const { addProjects } = useProject(); - - const addSubjects = async (subjects: StateSubject[]): Promise => { - subjects.forEach((subject: StateSubject) => { - const addSubjectAction: IngestionDispatchAction = { - type: SUBJECT_ACTIONS.ADD_SUBJECT, - subject - }; - - ingestionDispatch(addSubjectAction); - }); - }; - - const addSubject = async (subject: StateSubject): Promise => { - const alreadyExists = !!lodash.find(subjects, { arkId: subject.arkId }); - - if (!alreadyExists) { - const addSubjectAction: IngestionDispatchAction = { - type: SUBJECT_ACTIONS.ADD_SUBJECT, - subject - }; - - ingestionDispatch(addSubjectAction); - - const selectedSubjects = [...subjects, subject]; - updateProjectsAndItemsForSubjects(selectedSubjects, addItems, addProjects); - } else { - toast.info(`Subject ${subject.name} has already been added`); - } - }; - - const removeSubject = (arkId: string) => { - const removeSubjectAction: IngestionDispatchAction = { - type: SUBJECT_ACTIONS.REMOVE_SUBJECT, - arkId - }; - - ingestionDispatch(removeSubjectAction); - - const selectedSubjects = lodash.filter(subjects, subject => subject.arkId !== arkId); - updateProjectsAndItemsForSubjects(selectedSubjects, addItems, addProjects); - }; - - return { - addSubject, - addSubjects, - removeSubject - }; -} - -async function updateProjectsAndItemsForSubjects(selectedSubjects: StateSubject[], addItems, addProjects): Promise { - if (!selectedSubjects.length) { - addItems([]); - addProjects([]); - return; - } - - const idSubjects = selectedSubjects.map(({ id }) => id); - - const variables = { - input: { - idSubjects - } - }; - - try { - const projectsQueryResult: ApolloQueryResult = await apolloClient.query({ - query: GetIngestionProjectsForSubjectsDocument, - variables - }); - - const { data } = projectsQueryResult; - if (data) { - const { Project: foundProjects } = data.getIngestionProjectsForSubjects; - - const projects: StateProject[] = foundProjects.map((project: Project, index: number) => parseProjectToState(project, !index)); - - addProjects(projects); - } - } catch (error) { - toast.error('Failed to get projects for subjects'); - } - - try { - const itemsQueryResult: ApolloQueryResult = await apolloClient.query({ - query: GetIngestionItemsForSubjectsDocument, - variables - }); - - const { data } = itemsQueryResult; - - if (data) { - const { Item: foundItems } = data.getIngestionItemsForSubjects; - - const items: StateItem[] = foundItems.map((item: Item, index: number) => parseItemToState(item, false, index)); - - addItems(items); - } - } catch (error) { - toast.error('Failed to get items for subjects'); - } -} - -export default useSubject; diff --git a/client/src/pages/Ingestion/hooks/useVocabularyEntries.ts b/client/src/pages/Ingestion/hooks/useVocabularyEntries.ts deleted file mode 100644 index 89099ff50..000000000 --- a/client/src/pages/Ingestion/hooks/useVocabularyEntries.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { useContext } from 'react'; -import { AppContext, IngestionDispatchAction, StateVocabulary, VocabularyOption, VOCABULARY_ACTIONS } from '../../../context'; -import { apolloClient } from '../../../graphql'; -import { GetVocabularyEntriesDocument } from '../../../types/graphql'; -import { eVocabularySetID } from '../../../types/server'; -import lodash from 'lodash'; - -type AssetType = { - photogrammetry: boolean; - bagit: boolean; -}; - -interface UseVocabularyEntries { - updateVocabularyEntries: () => Promise; - getEntries: (eVocabularySetID: eVocabularySetID) => VocabularyOption[]; - getInitialEntry: (eVocabularySetID: eVocabularySetID) => number | null; - getInitialEntryWithVocabularies: (vocabularies: StateVocabulary, eVocabularySetID: eVocabularySetID) => number | null; - getAssetType: (idVocabulary: number) => AssetType; -} - -function useVocabularyEntries(): UseVocabularyEntries { - const { - ingestion: { vocabularies }, - ingestionDispatch - } = useContext(AppContext); - - const variables = { - input: { - eVocabSetIDs: [ - eVocabularySetID.eIdentifierIdentifierType, - eVocabularySetID.eCaptureDataDatasetType, - eVocabularySetID.eCaptureDataItemPositionType, - eVocabularySetID.eCaptureDataFocusType, - eVocabularySetID.eCaptureDataLightSourceType, - eVocabularySetID.eCaptureDataBackgroundRemovalMethod, - eVocabularySetID.eCaptureDataClusterType, - eVocabularySetID.eCaptureDataFileVariantType, - eVocabularySetID.eAssetAssetType - ] - } - }; - - const updateVocabularyEntries = async (): Promise => { - const { data } = await apolloClient.query({ - query: GetVocabularyEntriesDocument, - variables - }); - - const { VocabularyEntries } = data.getVocabularyEntries; - - const updatedVocabularyEntries = new Map(); - - VocabularyEntries.forEach(({ eVocabSetID, Vocabulary }) => { - updatedVocabularyEntries.set(eVocabSetID, Vocabulary); - }); - - const updateVocabulariesAction: IngestionDispatchAction = { - type: VOCABULARY_ACTIONS.ADD_VOCABULARIES, - vocabularies: updatedVocabularyEntries - }; - - ingestionDispatch(updateVocabulariesAction); - - return updatedVocabularyEntries; - }; - - const getEntries = (eVocabularySetID: eVocabularySetID): VocabularyOption[] => { - const vocabularyEntry = vocabularies.get(eVocabularySetID); - - let selectOptions: VocabularyOption[] = []; - - if (vocabularyEntry) { - selectOptions = vocabularyEntry; - } - - return selectOptions; - }; - - const getInitialEntry = (eVocabularySetID: eVocabularySetID): number | null => { - const vocabularyEntry = vocabularies.get(eVocabularySetID); - - if (vocabularyEntry && vocabularyEntry.length) { - return vocabularyEntry[0].idVocabulary; - } - - return null; - }; - - const getInitialEntryWithVocabularies = (vocabularies: StateVocabulary, eVocabularySetID: eVocabularySetID): number | null => { - const vocabularyEntry = vocabularies.get(eVocabularySetID); - - if (vocabularyEntry && vocabularyEntry.length) { - return vocabularyEntry[0].idVocabulary; - } - - return null; - }; - - const getAssetType = (idVocabulary: number): AssetType => { - const vocabularyEntry = vocabularies.get(eVocabularySetID.eAssetAssetType); - - const assetType: AssetType = { - photogrammetry: false, - bagit: false - }; - - if (vocabularyEntry) { - const foundVocabulary = lodash.find(vocabularyEntry, option => option.idVocabulary === idVocabulary); - - if (foundVocabulary) { - assetType.photogrammetry = foundVocabulary.Term.toLowerCase().includes('photogrammetry'); - assetType.bagit = foundVocabulary.Term.toLowerCase().includes('bulk'); - } - } - - return assetType; - }; - - return { - updateVocabularyEntries, - getEntries, - getInitialEntry, - getInitialEntryWithVocabularies, - getAssetType - }; -} - -export default useVocabularyEntries; diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index e8db69919..10598be0d 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -1,6 +1,6 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Redirect, useRouteMatch } from 'react-router'; import { PrivateRoute } from '../../components'; import { IngestionSidebarMenu, IngestionSidebarOption } from './components/IngestionSidebar'; @@ -8,9 +8,9 @@ import { HOME_ROUTES, INGESTION_ROUTE, INGESTION_ROUTES_TYPE, INGESTION_PARAMS_T import Uploads from './components/Uploads'; import Metadata from './components/Metadata'; import SubjectItem from './components/SubjectItem'; -import { AppContext } from '../../context'; import { Prompt } from 'react-router-dom'; import useIngest from './hooks/useIngest'; +import { useMetadata } from '../../store'; const useStyles = makeStyles(() => ({ container: { @@ -22,7 +22,7 @@ const useStyles = makeStyles(() => ({ function Ingestion(): React.ReactElement { const classes = useStyles(); const { path } = useRouteMatch(); - const { ingestion: { metadatas } } = useContext(AppContext); + const { metadatas } = useMetadata(); const { ingestionReset } = useIngest(); const [options, setOptions] = useState([]); diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 60b7bf19a..31ff43e9b 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -1,3 +1,8 @@ -import useUser from './user'; - -export { useUser }; +export * from './user'; +export * from './vocabulary'; +export * from './upload'; +export * from './subject'; +export * from './project'; +export * from './item'; +export * from './metadata'; +export * from './utils'; diff --git a/client/src/store/item.ts b/client/src/store/item.ts new file mode 100644 index 000000000..20f9c2a1f --- /dev/null +++ b/client/src/store/item.ts @@ -0,0 +1,88 @@ +import create from 'zustand'; +import lodash from 'lodash'; + +export const defaultItem: StateItem = { + id: 'default', + name: '', + entireSubject: false, + selected: true +}; + +export type StateItem = { + id: string; + name: string; + entireSubject: boolean; + selected: boolean; +}; + +type ItemStore = { + items: StateItem[]; + loading: boolean; + getSelectedItem: () => StateItem | undefined; + addItems: (items: StateItem[]) => void; + updateItem: (item: StateItem) => void; + reset: () => void; +}; + +export const useItem = create((set, get) => ({ + items: [defaultItem], + loading: false, + getSelectedItem: () => { + const { items } = get(); + return lodash.find(items, { selected: true }); + }, + addItems: (fetchedItems: StateItem[]): void => { + const { items } = get(); + const currentDefaultItem = lodash.find(items, { id: defaultItem.id }); + + if (currentDefaultItem) { + if (!fetchedItems.length) { + const selectedDefaultItem = { ...currentDefaultItem, selected: true }; + set({ items: [selectedDefaultItem] }); + return; + } + + const newItemSelected = lodash.find(fetchedItems, { selected: true }); + + if (newItemSelected) { + currentDefaultItem.selected = false; + } + + const newItems: StateItem[] = [currentDefaultItem].concat(fetchedItems); + + set({ items: newItems }); + } + }, + updateItem: (item: StateItem) => { + const { items, getSelectedItem } = get(); + const { selected } = item; + + let updatedItems = items; + + const mapItems = (newItem: StateItem): StateItem[] => + lodash.map(updatedItems, item => { + if (item.id === newItem.id) { + return newItem; + } + return item; + }); + + if (selected) { + const alreadySelected: StateItem | undefined = getSelectedItem(); + if (alreadySelected) { + const unselectedItem = { + ...alreadySelected, + selected: false + }; + updatedItems = mapItems(unselectedItem); + set({ items: updatedItems }); + } + } + + updatedItems = mapItems(item); + set({ items: updatedItems }); + }, + reset: (): void => { + set({ items: [defaultItem], loading: false }); + } +})); diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts new file mode 100644 index 000000000..ee9083898 --- /dev/null +++ b/client/src/store/metadata.ts @@ -0,0 +1,419 @@ +import { ApolloQueryResult } from '@apollo/client'; +import lodash from 'lodash'; +import { toast } from 'react-toastify'; +import create from 'zustand'; +import { apolloClient } from '../graphql'; +import { + AreCameraSettingsUniformDocument, + AssetVersionContent, + GetAssetVersionsDetailsDocument, + GetAssetVersionsDetailsQuery, + GetContentsForAssetVersionsDocument, + IngestFolder, + Project +} from '../types/graphql'; +import { eVocabularySetID } from '../types/server'; +import { useItem, StateItem } from './item'; +import { useProject, StateProject } from './project'; +import { useSubject, StateSubject } from './subject'; +import { useUpload, FileId, IngestionFile } from './upload'; +import { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from './utils'; +import { useVocabulary } from './vocabulary'; + +type MetadataInfo = { + metadata: StateMetadata; + metadataIndex: number; + isLast: boolean; +}; + +type FieldErrors = { + photogrammetry: { + dateCaptured: boolean; + datasetType: boolean; + }; +}; + +export type MetadataFieldValue = string | number | boolean | Date; + +type MetadataUpdate = { + valid: boolean; + selectedFiles: boolean; +}; + +export type StateIdentifier = { + id: number; + identifier: string; + identifierType: number | null; + selected: boolean; +}; + +export type StateFolder = { + id: number; + name: string; + variantType: number | null; +}; + +export type PhotogrammetryFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; + folders: StateFolder[]; + description: string; + dateCaptured: Date; + datasetType: number | null; + datasetFieldId: number | null; + itemPositionType: number | null; + itemPositionFieldId: number | null; + itemArrangementFieldId: number | null; + focusType: number | null; + lightsourceType: number | null; + backgroundRemovalMethod: number | null; + clusterType: number | null; + clusterGeometryFieldId: number | null; + cameraSettingUniform: boolean; + directory: string; +}; + +export const defaultPhotogrammetryFields: PhotogrammetryFields = { + systemCreated: true, + identifiers: [], + folders: [], + description: '', + dateCaptured: new Date(), + datasetType: null, + datasetFieldId: null, + itemPositionType: null, + itemPositionFieldId: null, + itemArrangementFieldId: null, + focusType: null, + lightsourceType: null, + backgroundRemovalMethod: null, + clusterType: null, + clusterGeometryFieldId: null, + cameraSettingUniform: false, + directory: '' +}; + +export type StateMetadata = { + photogrammetry: PhotogrammetryFields; + file: IngestionFile; +}; + +type MetadataStore = { + metadatas: StateMetadata[]; + getStateFolders: (folders: IngestFolder[]) => StateFolder[]; + getInitialStateFolders: (folders: string[]) => StateFolder[]; + getSelectedIdentifiers: (metadata: StateMetadata) => StateIdentifier[] | undefined; + getFieldErrors: (metadata: StateMetadata) => FieldErrors; + getCurrentMetadata: (id: FileId) => StateMetadata | undefined; + getMetadataInfo: (id: FileId) => MetadataInfo; + updateMetadataSteps: () => Promise; + updatePhotogrammetryFields: (metadataIndex: number, values: PhotogrammetryFields) => void; + updateMetadataFolders: () => Promise; + updateCameraSettings: (metadatas: StateMetadata[]) => Promise; + reset: () => void; +}; + +export const useMetadata = create((set, get) => ({ + metadatas: [], + getSelectedIdentifiers: (metadata: StateMetadata): StateIdentifier[] | undefined => lodash.filter(metadata.photogrammetry.identifiers, { selected: true }), + getFieldErrors: (metadata: StateMetadata): FieldErrors => { + const { getAssetType } = useVocabulary.getState(); + const errors: FieldErrors = { + photogrammetry: { + dateCaptured: false, + datasetType: false + } + }; + + const { file } = metadata; + const { type } = file; + + const assetType = getAssetType(type); + + if (assetType.photogrammetry) { + errors.photogrammetry.dateCaptured = metadata.photogrammetry.dateCaptured.toString() === 'Invalid Date'; + errors.photogrammetry.datasetType = metadata.photogrammetry.datasetType === null; + } + + return errors; + }, + getCurrentMetadata: (id: FileId): StateMetadata | undefined => { + const { metadatas } = get(); + return metadatas.find(({ file }) => file.id === id); + }, + getMetadataInfo: (id: FileId): MetadataInfo => { + const { metadatas } = get(); + const metadataLength = metadatas.length; + const metadata: StateMetadata | undefined = metadatas.find(({ file }) => file.id === id); + const metadataIndex = lodash.indexOf(metadatas, metadata); + const isLast = metadataIndex + 1 === metadataLength; + + return { + metadata: metadatas[metadataIndex], + metadataIndex, + isLast + }; + }, + updateMetadataSteps: async (): Promise => { + const { getStateFolders } = get(); + const { completed, getSelectedFiles } = useUpload.getState(); + const { getInitialEntry } = useVocabulary.getState(); + const { addSubjects } = useSubject.getState(); + const { addProjects } = useProject.getState(); + const { addItems } = useItem.getState(); + + const selectedFiles = getSelectedFiles(completed, true); + + if (!selectedFiles.length) { + return { + valid: false, + selectedFiles: false + }; + } + + const idAssetVersions: number[] = lodash.map(selectedFiles, ({ id }) => parseFileId(id)); + + const defaultIdentifier: StateIdentifier = { + id: 0, + identifier: '', + identifierType: getInitialEntry(eVocabularySetID.eIdentifierIdentifierType), + selected: false + }; + + const defaultVocabularyFields = { + ...defaultPhotogrammetryFields, + datasetType: getInitialEntry(eVocabularySetID.eCaptureDataDatasetType), + identifiers: [defaultIdentifier] + }; + + try { + const assetVersionDetailsQuery: ApolloQueryResult = await apolloClient.query({ + query: GetAssetVersionsDetailsDocument, + variables: { + input: { + idAssetVersions + } + } + }); + + const { data } = assetVersionDetailsQuery; + + if (data) { + const { + getAssetVersionsDetails: { Details } + } = data; + + const subjects: StateSubject[] = []; + const projects: StateProject[] = []; + const items: StateItem[] = []; + const metadatas: StateMetadata[] = []; + + for (let index = 0; index < Details.length; index++) { + const { idAssetVersion, SubjectUnitIdentifier: foundSubjectUnitIdentifier, Project: foundProject, Item: foundItem, CaptureDataPhoto } = Details[index]; + + if (foundSubjectUnitIdentifier) { + const subject: StateSubject = parseSubjectUnitIdentifierToState(foundSubjectUnitIdentifier); + subjects.push(subject); + } + + if (foundProject) { + const stateProjects: StateProject[] = foundProject.map((project: Project, index: number) => parseProjectToState(project, !index)); + projects.push(...stateProjects); + } + + if (foundItem) { + const item: StateItem = parseItemToState(foundItem, !index, index); + items.push(item); + } + + let metadataStep: StateMetadata; + const file = completed.find((file: IngestionFile) => parseFileId(file.id) === idAssetVersion); + + if (!file) { + toast.error('Ingestion file not found'); + throw new Error(); + } + + if (CaptureDataPhoto) { + const { identifiers, folders } = CaptureDataPhoto; + const parsedIdentifiers: StateIdentifier[] = identifiers.map( + ({ identifier, identifierType }, index): StateIdentifier => ({ + id: index, + identifier, + identifierType, + selected: true + }) + ); + + const stateIdentifiers = parsedIdentifiers.length ? parsedIdentifiers : defaultVocabularyFields.identifiers; + + metadataStep = { + file, + photogrammetry: { + ...defaultVocabularyFields, + ...(CaptureDataPhoto && { + ...CaptureDataPhoto, + dateCaptured: new Date(CaptureDataPhoto.dateCaptured), + folders: getStateFolders(folders), + identifiers: stateIdentifiers + }) + } + }; + + metadatas.push(metadataStep); + } else { + metadataStep = { + file, + photogrammetry: { + ...defaultVocabularyFields + } + }; + metadatas.push(metadataStep); + } + } + + addSubjects(lodash.uniqBy(subjects, 'arkId')); + addProjects(projects); + addItems(lodash.uniqBy(items, 'name')); + + set({ metadatas }); + + return { + valid: true, + selectedFiles: true + }; + } + } catch { + toast.error('Failed to ingest selected files, please try again later'); + } + + return { + valid: false, + selectedFiles: true + }; + }, + updatePhotogrammetryFields: (metadataIndex: number, values: PhotogrammetryFields): void => { + const { metadatas } = get(); + const updatedMetadatas = lodash.map([...metadatas], (metadata: StateMetadata, index: number) => { + if (index === metadataIndex) { + return { + ...metadata, + photogrammetry: { + ...values + } + }; + } + + return metadata; + }); + + set({ metadatas: updatedMetadatas }); + }, + getStateFolders: (folders: IngestFolder[]): StateFolder[] => { + const stateFolders: StateFolder[] = folders.map(({ name, variantType }, index: number) => ({ + id: index, + name, + variantType + })); + + return stateFolders; + }, + getInitialStateFolders: (folders: string[]): StateFolder[] => { + const { getInitialEntry } = useVocabulary.getState(); + const stateFolders: StateFolder[] = folders.map((folder, index: number) => ({ + id: index, + name: folder, + variantType: getInitialEntry(eVocabularySetID.eCaptureDataFileVariantType) + })); + + return stateFolders; + }, + updateMetadataFolders: async (): Promise => { + const { metadatas, getInitialStateFolders, updateCameraSettings } = get(); + const idAssetVersions: number[] = metadatas.map(({ file: { id } }) => parseFileId(id)); + + const variables = { + input: { + idAssetVersions + } + }; + + const { data } = await apolloClient.query({ + query: GetContentsForAssetVersionsDocument, + variables + }); + + const { getContentsForAssetVersions } = data; + + const { AssetVersionContent: foundAssetVersionContent } = getContentsForAssetVersions; + + let updatedMetadatas = await updateCameraSettings(metadatas); + + foundAssetVersionContent.forEach(({ idAssetVersion, folders }: AssetVersionContent) => { + updatedMetadatas = updatedMetadatas.map(metadata => { + const { file, photogrammetry } = metadata; + const fileId = parseFileId(file.id); + + if (fileId === idAssetVersion) { + if (photogrammetry.folders.length) { + return metadata; + } + + const stateFolders: StateFolder[] = getInitialStateFolders(folders); + + return { + ...metadata, + photogrammetry: { + ...photogrammetry, + folders: stateFolders + } + }; + } + + return metadata; + }); + }); + + set({ metadatas: updatedMetadatas }); + }, + updateCameraSettings: async (metadatas: StateMetadata[]): Promise => { + const { getAssetType } = useVocabulary.getState(); + + const updatedMetadatas = metadatas.slice(); + + for (let index = 0; index < updatedMetadatas.length; index++) { + const metadata = updatedMetadatas[index]; + const { file, photogrammetry } = metadata; + const idAssetVersion = parseFileId(file.id); + + const assetType = getAssetType(file.type); + + if (assetType.photogrammetry) { + const variables = { + input: { + idAssetVersion + } + }; + + try { + const { data } = await apolloClient.query({ + query: AreCameraSettingsUniformDocument, + variables + }); + + const { areCameraSettingsUniform } = data; + const { isUniform } = areCameraSettingsUniform; + + photogrammetry.cameraSettingUniform = isUniform; + } catch { + toast.error('Failed to retrieve camera settings details'); + } + } + } + + return updatedMetadatas; + }, + reset: () => { + set({ metadatas: [] }); + } +})); diff --git a/client/src/store/project.ts b/client/src/store/project.ts new file mode 100644 index 000000000..fcf821db5 --- /dev/null +++ b/client/src/store/project.ts @@ -0,0 +1,70 @@ +import create from 'zustand'; +import lodash from 'lodash'; + +export type StateProject = { + id: number; + name: string; + selected: boolean; +}; + +type ProjectStore = { + projects: StateProject[]; + loading: boolean; + getSelectedProject: () => StateProject | undefined; + addProjects: (projects: StateProject[]) => void; + updateSelectedProject: (id: number) => void; + updateProject: (project: StateProject) => void; + reset: () => void; +}; + +export const useProject = create((set, get) => ({ + projects: [], + loading: false, + getSelectedProject: () => { + const { projects } = get(); + return lodash.find(projects, { selected: true }); + }, + addProjects: (fetchedProjects: StateProject[]) => { + if (!fetchedProjects.length) return; + set({ projects: fetchedProjects }); + }, + updateSelectedProject: (id: number): void => { + const { projects, getSelectedProject, updateProject } = get(); + const project: StateProject | undefined = lodash.find(projects, { id }); + + if (project) { + const { selected } = project; + if (!selected) { + const alreadySelected: StateProject | undefined = getSelectedProject(); + + if (alreadySelected) { + const unselectedProject = { + ...alreadySelected, + selected: false + }; + + updateProject(unselectedProject); + } + project.selected = true; + } + + updateProject(project); + } + }, + updateProject: (project: StateProject): void => { + const { projects } = get(); + + const updatedProjects = (newProject: StateProject) => + lodash.map(projects, project => { + if (project.id === newProject.id) { + return newProject; + } + return project; + }); + + set({ projects: updatedProjects(project) }); + }, + reset: () => { + set({ projects: [], loading: false }); + } +})); diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts new file mode 100644 index 000000000..6863f061f --- /dev/null +++ b/client/src/store/subject.ts @@ -0,0 +1,121 @@ +import create from 'zustand'; +import lodash from 'lodash'; +import { ApolloQueryResult } from '@apollo/client'; +import { toast } from 'react-toastify'; +import { parseProjectToState, parseItemToState } from './utils'; +import { apolloClient } from '../graphql'; +import { + GetIngestionProjectsForSubjectsQuery, + GetIngestionProjectsForSubjectsDocument, + Project, + GetIngestionItemsForSubjectsQuery, + GetIngestionItemsForSubjectsDocument, + Item +} from '../types/graphql'; +import { useItem, StateItem } from './item'; +import { useProject, StateProject } from './project'; + +export type StateSubject = { + id: number; + arkId: string; + unit: string; + name: string; +}; + +type SubjectStore = { + subjects: StateSubject[]; + addSubject: (subject: StateSubject) => Promise; + addSubjects: (subjects: StateSubject[]) => void; + removeSubject: (arkId: string) => void; + updateProjectsAndItemsForSubjects: (selectedSubjects: StateSubject[]) => Promise; + reset: () => void; +}; + +export const useSubject = create((set, get) => ({ + subjects: [], + addSubjects: async (fetchedSubjects: StateSubject[]): Promise => { + const { subjects } = get(); + const newSubjects: StateSubject[] = lodash.concat(subjects, fetchedSubjects); + set({ subjects: newSubjects }); + }, + addSubject: async (subject: StateSubject): Promise => { + const { subjects, addSubjects, updateProjectsAndItemsForSubjects } = get(); + const alreadyExists = !!lodash.find(subjects, { arkId: subject.arkId }); + + if (!alreadyExists) { + addSubjects([subject]); + const selectedSubjects = [...subjects, subject]; + + updateProjectsAndItemsForSubjects(selectedSubjects); + } else { + toast.info(`Subject ${subject.name} has already been added`); + } + }, + removeSubject: (arkId: string) => { + const { subjects, updateProjectsAndItemsForSubjects } = get(); + + const updatedSubjects = lodash.filter(subjects, (subject: StateSubject) => subject.arkId !== arkId); + set({ subjects: updatedSubjects }); + + const selectedSubjects = lodash.filter(subjects, subject => subject.arkId !== arkId); + updateProjectsAndItemsForSubjects(selectedSubjects); + }, + updateProjectsAndItemsForSubjects: async (selectedSubjects: StateSubject[]): Promise => { + const { addItems } = useItem.getState(); + const { addProjects } = useProject.getState(); + + if (!selectedSubjects.length) { + addItems([]); + addProjects([]); + return; + } + + const idSubjects = selectedSubjects.map(({ id }) => id); + + const variables = { + input: { + idSubjects + } + }; + + try { + const projectsQueryResult: ApolloQueryResult = await apolloClient.query({ + query: GetIngestionProjectsForSubjectsDocument, + variables + }); + + const { data } = projectsQueryResult; + if (data) { + const { Project: foundProjects } = data.getIngestionProjectsForSubjects; + + const projects: StateProject[] = foundProjects.map((project: Project, index: number) => parseProjectToState(project, !index)); + + addProjects(projects); + } + } catch (error) { + toast.error('Failed to get projects for subjects'); + } + + try { + const itemsQueryResult: ApolloQueryResult = await apolloClient.query({ + query: GetIngestionItemsForSubjectsDocument, + variables + }); + + const { data } = itemsQueryResult; + + if (data) { + const { Item: foundItems } = data.getIngestionItemsForSubjects; + + const items: StateItem[] = foundItems.map((item: Item, index: number) => parseItemToState(item, false, index)); + + addItems(items); + } + } catch (error) { + toast.error('Failed to get items for subjects'); + } + }, + reset: () => { + set({ subjects: [] }); + } +})); diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts new file mode 100644 index 000000000..999261c24 --- /dev/null +++ b/client/src/store/upload.ts @@ -0,0 +1,305 @@ +import create from 'zustand'; +import lodash from 'lodash'; +import { toast } from 'react-toastify'; +import { eVocabularySetID } from '../types/server'; +import { generateFileId } from '../utils/upload'; +import { useVocabulary } from './vocabulary'; +import { apolloClient, apolloUploader } from '../graphql'; +import { DiscardUploadedAssetVersionsDocument, DiscardUploadedAssetVersionsMutation, UploadAssetDocument, UploadAssetMutation, UploadStatus } from '../types/graphql'; +import { FetchResult } from '@apollo/client'; +import { parseFileId } from './utils'; + +export type FileId = string; + +export enum FileUploadStatus { + READY = 'READY', + UPLOADING = 'UPLOADING', + COMPLETE = 'COMPLETE', + CANCELLED = 'CANCELLED', + FAILED = 'FAILED' +} + +export type IngestionFile = { + id: FileId; + size: number; + name: string; + file: File; + type: number; + status: FileUploadStatus; + progress: number; + selected: boolean; + cancel: (() => void) | null; +}; + +type UploadStore = { + completed: IngestionFile[]; + pending: IngestionFile[]; + loading: boolean; + getSelectedFiles: (files: IngestionFile[], selected: boolean) => IngestionFile[]; + loadPending: (acceptedFiles: File[]) => void; + loadCompleted: (completed: IngestionFile[]) => void; + selectFile: (id: FileId, selected: boolean) => void; + startUpload: (id: FileId) => void; + cancelUpload: (id: FileId) => void; + retryUpload: (id: FileId) => void; + removeUpload: (id: FileId) => void; + startUploadTransfer: (ingestionFile: IngestionFile) => void; + changeAssetType: (id: FileId, assetType: number) => void; + discardFiles: () => Promise; + removeSelectedUploads: () => void; + reset: () => void; +}; + +export const useUpload = create((set, get) => ({ + completed: [], + pending: [], + loading: true, + getSelectedFiles: (files: IngestionFile[], selected: boolean): IngestionFile[] => lodash.filter(files, file => file.selected === selected), + loadPending: (acceptedFiles: File[]) => { + const { pending } = get(); + + if (acceptedFiles.length) { + const ingestionFiles: IngestionFile[] = []; + acceptedFiles.forEach((file: File): void => { + const id = generateFileId(); + const alreadyContains = !!lodash.find(pending, { id }); + + const { name, size } = file; + const { getInitialEntry } = useVocabulary.getState(); + const type = getInitialEntry(eVocabularySetID.eAssetAssetType); + + if (!type) { + toast.error(`Vocabulary for file ${name} not found`); + return; + } + + if (!alreadyContains) { + const ingestionFile = { + id, + file, + name, + size, + status: FileUploadStatus.READY, + progress: 0, + type, + selected: false, + cancel: null + }; + + ingestionFiles.push(ingestionFile); + } else { + toast.info(`${file.name} was already loaded`); + } + }); + + const updatedPendingFiles = lodash.concat(pending, ingestionFiles); + set({ pending: updatedPendingFiles }); + } + }, + loadCompleted: (completed: IngestionFile[]): void => { + set({ completed, loading: false }); + }, + selectFile: (id: FileId, selected: boolean) => { + const { completed } = get(); + const updatedCompleted = lodash.forEach(completed, file => { + if (file.id === id) { + lodash.set(file, 'selected', selected); + } + }); + + set({ completed: updatedCompleted }); + }, + startUpload: (id: FileId) => { + const { pending, startUploadTransfer } = get(); + const file = getFile(id, pending); + + if (file) { + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'progress', 0); + lodash.set(file, 'status', FileUploadStatus.UPLOADING); + } + }); + + set({ pending: updatedPending }); + startUploadTransfer(file); + } + }, + retryUpload: (id: FileId): void => { + const { pending, startUploadTransfer } = get(); + const file = getFile(id, pending); + if (file) { + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'status', FileUploadStatus.UPLOADING); + } + }); + + set({ pending: updatedPending }); + startUploadTransfer(file); + } + }, + cancelUpload: (id: FileId): void => { + const { pending } = get(); + const file = getFile(id, pending); + + if (file) { + if (file.status === FileUploadStatus.UPLOADING) { + const { cancel } = file; + if (cancel) { + cancel(); + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'status', FileUploadStatus.CANCELLED); + } + }); + set({ pending: updatedPending }); + toast.warn('Upload has been cancelled'); + } + } + } + }, + removeUpload: (id: FileId): void => { + const { pending } = get(); + const updatedPending = pending.filter(file => file.id !== id); + set({ pending: updatedPending }); + }, + startUploadTransfer: async (ingestionFile: IngestionFile) => { + const { pending } = get(); + const { id, file, type } = ingestionFile; + + try { + const onProgress = (event: ProgressEvent) => { + const { loaded, total } = event; + const progress = Math.floor((loaded / total) * 100); + const updateProgress = !(progress % 5); + + if (updateProgress) { + const updatedPendingProgress = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'progress', progress); + } + }); + set({ pending: updatedPendingProgress }); + } + }; + + const onCancel = (cancel: () => void) => { + const updatedPendingProgress = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'cancel', cancel); + } + }); + set({ pending: updatedPendingProgress }); + }; + + const { data } = await apolloUploader({ + mutation: UploadAssetDocument, + variables: { file, type }, + refetchQueries: ['getUploadedAssetVersion'], + useUpload: true, + onProgress, + onCancel + }); + + const { uploadAsset }: UploadAssetMutation = data; + + if (uploadAsset) { + const { status, error } = uploadAsset; + + if (status === UploadStatus.Complete) { + const updatedPending = pending.filter(file => file.id !== id); + set({ pending: updatedPending }); + toast.success(`Upload finished for ${file.name}`); + } else if (status === UploadStatus.Failed) { + const errorMessage = error || `Upload failed for ${file.name}`; + toast.error(errorMessage); + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'status', FileUploadStatus.FAILED); + } + }); + set({ pending: updatedPending }); + } + } + } catch ({ message }) { + const file = getFile(id, pending); + + if (file) { + if (file.status !== FileUploadStatus.CANCELLED) { + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'status', FileUploadStatus.FAILED); + } + }); + set({ pending: updatedPending }); + } + } + } + }, + changeAssetType: (id: FileId, assetType: number): void => { + const { pending } = get(); + const updatedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'type', assetType); + } + }); + set({ pending: updatedPending }); + }, + discardFiles: async (): Promise => { + const { completed, getSelectedFiles } = get(); + const selectedFiles = getSelectedFiles(completed, true); + + if (!selectedFiles.length) { + toast.warn('Please select at least 1 file to discard'); + return; + } + + const isConfirmed = global.confirm('Do you want to discard selected items?'); + + if (!isConfirmed) return; + + const idAssetVersions: number[] = selectedFiles.map(({ id }) => parseFileId(id)); + + const discardMutationVariables = { + input: { + idAssetVersions + } + }; + + try { + const { data }: FetchResult = await apolloClient.mutate({ + mutation: DiscardUploadedAssetVersionsDocument, + variables: discardMutationVariables + }); + + if (data) { + const { discardUploadedAssetVersions } = data; + const { success } = discardUploadedAssetVersions; + + if (!success) { + toast.error('Failed to discard selected files'); + return; + } + + const updatedCompleted = getSelectedFiles(completed, false); + set({ completed: updatedCompleted }); + + toast.info('Selected files have been discarded'); + return; + } + } catch { + toast.error('Failed to discard selected files'); + } + }, + removeSelectedUploads: (): void => { + const { completed } = get(); + const updatedCompleted = completed.filter(({ selected }) => !selected); + set({ completed: updatedCompleted }); + }, + reset: (): void => { + set({ completed: [], pending: [], loading: true }); + } +})); + +const getFile = (id: FileId, files: IngestionFile[]) => lodash.find(files, { id }); diff --git a/client/src/store/user.ts b/client/src/store/user.ts index 80eaf3644..7df056995 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -4,14 +4,14 @@ import { apolloClient } from '../graphql'; import { QueryOptions } from '@apollo/client'; import API, { AuthResponseType } from '../api'; -type UserState = { +type UserStore = { user: User | null; initialize: () => Promise; login: (email: string, password: string) => Promise; logout: () => Promise; }; -const useUser = create((set, get) => ({ +export const useUser = create((set, get) => ({ user: null, initialize: async () => { const { user } = get(); @@ -61,5 +61,3 @@ async function getAuthenticatedUser(): Promise { return null; } } - -export default useUser; diff --git a/client/src/context/utils.ts b/client/src/store/utils.ts similarity index 89% rename from client/src/context/utils.ts rename to client/src/store/utils.ts index 7deb4de18..8739ad86e 100644 --- a/client/src/context/utils.ts +++ b/client/src/store/utils.ts @@ -1,5 +1,8 @@ import { Item, Project, SubjectUnitIdentifier, AssetVersion, Vocabulary } from '../types/graphql'; -import { StateSubject, StateItem, StateProject, IngestionFile, FileUploadStatus, FileId } from './ingestion'; +import { StateSubject } from './subject'; +import { StateItem } from './item'; +import { StateProject } from './project'; +import { IngestionFile, FileUploadStatus, FileId } from './upload'; export function parseFileId(id: FileId): number { return Number.parseInt(id, 10); diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts new file mode 100644 index 000000000..578eb4d23 --- /dev/null +++ b/client/src/store/vocabulary.ts @@ -0,0 +1,111 @@ +import create from 'zustand'; +import { apolloClient } from '../graphql'; +import { GetVocabularyEntriesDocument, Vocabulary } from '../types/graphql'; +import { eVocabularySetID } from '../types/server'; +import lodash from 'lodash'; + +export type VocabularyOption = Pick; +export type StateVocabulary = Map; + +type AssetType = { + photogrammetry: boolean; + bagit: boolean; +}; + +type VocabularyStore = { + vocabularies: Map; + updateVocabularyEntries: () => Promise; + getEntries: (eVocabularySetID: eVocabularySetID) => VocabularyOption[]; + getInitialEntry: (eVocabularySetID: eVocabularySetID) => number | null; + getInitialEntryWithVocabularies: (vocabularies: StateVocabulary, eVocabularySetID: eVocabularySetID) => number | null; + getAssetType: (idVocabulary: number) => AssetType; +}; + +export const useVocabulary = create((set, get) => ({ + vocabularies: new Map(), + updateVocabularyEntries: async (): Promise => { + const { data } = await apolloClient.query({ + query: GetVocabularyEntriesDocument, + variables + }); + + const { VocabularyEntries } = data.getVocabularyEntries; + + const vocabularies = new Map(); + + VocabularyEntries.forEach(({ eVocabSetID, Vocabulary }) => { + vocabularies.set(eVocabSetID, Vocabulary); + }); + + set({ vocabularies }); + + return vocabularies; + }, + getEntries: (eVocabularySetID: eVocabularySetID): VocabularyOption[] => { + const { vocabularies } = get(); + const vocabularyEntry = vocabularies.get(eVocabularySetID); + + let options: VocabularyOption[] = []; + + if (vocabularyEntry) { + options = vocabularyEntry; + } + + return options; + }, + getInitialEntry: (eVocabularySetID: eVocabularySetID): number | null => { + const { vocabularies } = get(); + const vocabularyEntry = vocabularies.get(eVocabularySetID); + + if (vocabularyEntry && vocabularyEntry.length) { + return vocabularyEntry[0].idVocabulary; + } + + return null; + }, + getInitialEntryWithVocabularies: (vocabularies: StateVocabulary, eVocabularySetID: eVocabularySetID): number | null => { + const vocabularyEntry = vocabularies.get(eVocabularySetID); + + if (vocabularyEntry && vocabularyEntry.length) { + return vocabularyEntry[0].idVocabulary; + } + + return null; + }, + getAssetType: (idVocabulary: number): AssetType => { + const { vocabularies } = get(); + const vocabularyEntry = vocabularies.get(eVocabularySetID.eAssetAssetType); + + const assetType: AssetType = { + photogrammetry: false, + bagit: false + }; + + if (vocabularyEntry) { + const foundVocabulary = lodash.find(vocabularyEntry, option => option.idVocabulary === idVocabulary); + + if (foundVocabulary) { + assetType.photogrammetry = foundVocabulary.Term.toLowerCase().includes('photogrammetry'); + assetType.bagit = foundVocabulary.Term.toLowerCase().includes('bulk'); + } + } + + return assetType; + } +})); + +const variables = { + input: { + eVocabSetIDs: [ + eVocabularySetID.eIdentifierIdentifierType, + eVocabularySetID.eCaptureDataDatasetType, + eVocabularySetID.eCaptureDataItemPositionType, + eVocabularySetID.eCaptureDataFocusType, + eVocabularySetID.eCaptureDataLightSourceType, + eVocabularySetID.eCaptureDataBackgroundRemovalMethod, + eVocabularySetID.eCaptureDataClusterType, + eVocabularySetID.eCaptureDataFileVariantType, + eVocabularySetID.eAssetAssetType + ] + } +}; From 6622bdd8216f1d2e9e3c2a1fc928bd3478a1b7c0 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 2 Oct 2020 18:32:45 +0530 Subject: [PATCH 13/22] strictly type state getters and setters --- client/src/store/item.ts | 4 ++-- client/src/store/metadata.ts | 4 ++-- client/src/store/project.ts | 4 ++-- client/src/store/subject.ts | 4 ++-- client/src/store/upload.ts | 4 ++-- client/src/store/user.ts | 4 ++-- client/src/store/vocabulary.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/src/store/item.ts b/client/src/store/item.ts index 20f9c2a1f..91482a71d 100644 --- a/client/src/store/item.ts +++ b/client/src/store/item.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; export const defaultItem: StateItem = { @@ -24,7 +24,7 @@ type ItemStore = { reset: () => void; }; -export const useItem = create((set, get) => ({ +export const useItem = create((set: SetState, get: GetState) => ({ items: [defaultItem], loading: false, getSelectedItem: () => { diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts index ee9083898..1a37e331f 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata.ts @@ -1,7 +1,7 @@ import { ApolloQueryResult } from '@apollo/client'; import lodash from 'lodash'; import { toast } from 'react-toastify'; -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import { apolloClient } from '../graphql'; import { AreCameraSettingsUniformDocument, @@ -113,7 +113,7 @@ type MetadataStore = { reset: () => void; }; -export const useMetadata = create((set, get) => ({ +export const useMetadata = create((set: SetState, get: GetState) => ({ metadatas: [], getSelectedIdentifiers: (metadata: StateMetadata): StateIdentifier[] | undefined => lodash.filter(metadata.photogrammetry.identifiers, { selected: true }), getFieldErrors: (metadata: StateMetadata): FieldErrors => { diff --git a/client/src/store/project.ts b/client/src/store/project.ts index fcf821db5..738ec5e93 100644 --- a/client/src/store/project.ts +++ b/client/src/store/project.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; export type StateProject = { @@ -17,7 +17,7 @@ type ProjectStore = { reset: () => void; }; -export const useProject = create((set, get) => ({ +export const useProject = create((set: SetState, get: GetState) => ({ projects: [], loading: false, getSelectedProject: () => { diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts index 6863f061f..fbfec7b28 100644 --- a/client/src/store/subject.ts +++ b/client/src/store/subject.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; import { ApolloQueryResult } from '@apollo/client'; import { toast } from 'react-toastify'; @@ -31,7 +31,7 @@ type SubjectStore = { reset: () => void; }; -export const useSubject = create((set, get) => ({ +export const useSubject = create((set: SetState, get: GetState) => ({ subjects: [], addSubjects: async (fetchedSubjects: StateSubject[]): Promise => { const { subjects } = get(); diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts index 999261c24..a34f187fd 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; import { toast } from 'react-toastify'; import { eVocabularySetID } from '../types/server'; @@ -50,7 +50,7 @@ type UploadStore = { reset: () => void; }; -export const useUpload = create((set, get) => ({ +export const useUpload = create((set: SetState, get: GetState) => ({ completed: [], pending: [], loading: true, diff --git a/client/src/store/user.ts b/client/src/store/user.ts index 7df056995..bd6a29534 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import { User, GetCurrentUserDocument } from '../types/graphql'; import { apolloClient } from '../graphql'; import { QueryOptions } from '@apollo/client'; @@ -11,7 +11,7 @@ type UserStore = { logout: () => Promise; }; -export const useUser = create((set, get) => ({ +export const useUser = create((set: SetState, get: GetState) => ({ user: null, initialize: async () => { const { user } = get(); diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 578eb4d23..a81a0d637 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -1,4 +1,4 @@ -import create from 'zustand'; +import create, { SetState, GetState } from 'zustand'; import { apolloClient } from '../graphql'; import { GetVocabularyEntriesDocument, Vocabulary } from '../types/graphql'; import { eVocabularySetID } from '../types/server'; @@ -21,7 +21,7 @@ type VocabularyStore = { getAssetType: (idVocabulary: number) => AssetType; }; -export const useVocabulary = create((set, get) => ({ +export const useVocabulary = create((set: SetState, get: GetState) => ({ vocabularies: new Map(), updateVocabularyEntries: async (): Promise => { const { data } = await apolloClient.query({ From 5d74ca98cfeaccc53872fd3b6d77f9ec269d09f5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 2 Oct 2020 18:48:11 +0530 Subject: [PATCH 14/22] fix file type select issue --- client/src/pages/Ingestion/components/Uploads/FileListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index a6e5d7169..4f21b9693 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -189,7 +189,7 @@ function FileListItem(props: FileListItemProps): React.ReactElement { `${selectedProject?.name || 'none'}`} onChange={({ target: { value } }) => updateSelectedProject(value as number)} diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index 2bdc2b762..e81faf4f9 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -44,11 +44,10 @@ function SubjectItem(): React.ReactElement { const [itemError, setItemError] = useState(false); const [metadataStepLoading, setMetadataStepLoading] = useState(false); - const getSelectedProject = useProject(state => state.getSelectedProject); - const getSelectedItem = useItem(state => state.getSelectedItem); const updateVocabularyEntries = useVocabulary(state => state.updateVocabularyEntries); const subjects = useSubject(state => state.subjects); - const projects = useProject(state => state.projects); + const [projects, projectsLoading, getSelectedProject] = useProject(state => [state.projects, state.loading, state.getSelectedProject]); + const [itemsLoading, getSelectedItem] = useItem(state => [state.loading, state.getSelectedItem]); const [metadatas, updateMetadataFolders] = useMetadata(state => [state.metadatas, state.updateMetadataFolders]); const selectedItem = getSelectedItem(); @@ -151,6 +150,7 @@ function SubjectItem(): React.ReactElement { - + diff --git a/client/src/store/item.ts b/client/src/store/item.ts index 56e7daedb..70673744f 100644 --- a/client/src/store/item.ts +++ b/client/src/store/item.ts @@ -21,6 +21,7 @@ type ItemStore = { getSelectedItem: () => StateItem | undefined; addItems: (items: StateItem[]) => void; updateItem: (item: StateItem) => void; + loadingItems: () => void; reset: () => void; }; @@ -38,7 +39,7 @@ export const useItem = create((set: SetState, get: GetStat if (currentDefaultItem) { if (!fetchedItems.length) { const selectedDefaultItem = { ...currentDefaultItem, selected: true }; - set({ items: [selectedDefaultItem] }); + set({ items: [selectedDefaultItem], loading: false }); return; } @@ -50,7 +51,7 @@ export const useItem = create((set: SetState, get: GetStat const newItems: StateItem[] = [currentDefaultItem].concat(fetchedItems); - set({ items: newItems }); + set({ items: newItems, loading: false }); } }, updateItem: (item: StateItem): void => { @@ -82,6 +83,9 @@ export const useItem = create((set: SetState, get: GetStat updatedItems = mapItems(item); set({ items: updatedItems }); }, + loadingItems: (): void => { + set({ loading: true }); + }, reset: (): void => { set({ items: [defaultItem], loading: false }); } diff --git a/client/src/store/project.ts b/client/src/store/project.ts index 738ec5e93..927ae84fe 100644 --- a/client/src/store/project.ts +++ b/client/src/store/project.ts @@ -14,6 +14,7 @@ type ProjectStore = { addProjects: (projects: StateProject[]) => void; updateSelectedProject: (id: number) => void; updateProject: (project: StateProject) => void; + loadingProjects: () => void; reset: () => void; }; @@ -26,7 +27,7 @@ export const useProject = create((set: SetState, get }, addProjects: (fetchedProjects: StateProject[]) => { if (!fetchedProjects.length) return; - set({ projects: fetchedProjects }); + set({ projects: fetchedProjects, loading: false }); }, updateSelectedProject: (id: number): void => { const { projects, getSelectedProject, updateProject } = get(); @@ -64,6 +65,9 @@ export const useProject = create((set: SetState, get set({ projects: updatedProjects(project) }); }, + loadingProjects: (): void => { + set({ loading: true }); + }, reset: () => { set({ projects: [], loading: false }); } diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts index 22f8e30f0..caab4ca91 100644 --- a/client/src/store/subject.ts +++ b/client/src/store/subject.ts @@ -61,8 +61,8 @@ export const useSubject = create((set: SetState, get updateProjectsAndItemsForSubjects(selectedSubjects); }, updateProjectsAndItemsForSubjects: async (selectedSubjects: StateSubject[]): Promise => { - const { addItems } = useItem.getState(); - const { addProjects } = useProject.getState(); + const { addProjects, loadingProjects } = useProject.getState(); + const { addItems, loadingItems } = useItem.getState(); if (!selectedSubjects.length) { addItems([]); @@ -79,6 +79,8 @@ export const useSubject = create((set: SetState, get }; try { + loadingProjects(); + loadingItems(); const projectsQueryResult: ApolloQueryResult = await apolloClient.query({ query: GetIngestionProjectsForSubjectsDocument, variables From 6bcdb0f77a427588ed40eaab1863a8e8bc073f34 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 6 Oct 2020 18:10:46 +0530 Subject: [PATCH 21/22] updated file motion --- .../src/pages/Ingestion/components/Uploads/FileListItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index 2dcbbb316..b21f068b1 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -156,8 +156,8 @@ function FileListItem(props: FileListItemProps): React.ReactElement { const uploadStatus = status.charAt(0) + status.slice(1).toLowerCase(); const variants = { - visible: { opacity: 1, y: 0 }, - hidden: { opacity: 0.5, y: 10 }, + visible: { opacity: 1, }, + hidden: { opacity: 0.5 }, }; return ( From b4fed617c9fec06d195da66fcf4d8dabc5ecf04a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 6 Oct 2020 18:26:48 +0530 Subject: [PATCH 22/22] improve repository tree performance, fix dissapearing node issue --- .../RepositoryTreeView/RepositoryTreeNode.tsx | 52 ++++++++++++------- .../RepositoryTreeView/StyledTreeItem.tsx | 10 ++-- .../components/RepositoryTreeView/index.tsx | 22 ++++++-- .../pages/Repository/hooks/useRepository.ts | 7 ++- client/src/pages/Repository/index.tsx | 2 +- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx index 759595337..fed653e9a 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx @@ -1,16 +1,17 @@ import React from 'react'; -import StyledTreeItem from './StyledTreeItem'; -import TreeViewContents from './TreeViewContents'; -import { eMetadata, eSystemObjectType } from '../../../../types/server'; -import { getRepositoryTreeNodeId, getSortedTreeEntries, getTreeColorVariant, getTreeViewColumns } from '../../../../utils/repository'; -import { useGetObjectChildren } from '../../hooks/useRepository'; import { AiOutlineFileText } from 'react-icons/ai'; +import { RepositoryFilter } from '../..'; +import { RepositoryIcon } from '../../../../components'; import { Colors } from '../../../../theme'; import { RepositoryColorVariant } from '../../../../theme/colors'; -import { RepositoryIcon } from '../../../../components'; -import { TreeViewColumn } from './StyledTreeItem'; -import { RepositoryFilter } from '../..'; import { NavigationResultEntry } from '../../../../types/graphql'; +import { eMetadata, eSystemObjectType } from '../../../../types/server'; +import { getRepositoryTreeNodeId, getSortedTreeEntries, getTreeColorVariant, getTreeViewColumns } from '../../../../utils/repository'; +import { useGetObjectChildren } from '../../hooks/useRepository'; +import StyledTreeItem, { TreeViewColumn } from './StyledTreeItem'; +import TreeViewContents from './TreeViewContents'; + +export type ExpandedNodeMap = Map; interface RepositoryTreeNodeProps { idSystemObject: number; @@ -21,37 +22,51 @@ interface RepositoryTreeNodeProps { icon: React.ReactNode; treeColumns: TreeViewColumn[]; filter: RepositoryFilter; + expandedNodes: ExpandedNodeMap; } function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement { - const { idSystemObject, idObject, name, objectType, icon, color, treeColumns, filter } = props; + const { idSystemObject, idObject, name, objectType, icon, color, treeColumns, filter, expandedNodes } = props; + const { + getObjectChildren, + getObjectChildrenData, + getObjectChildrenLoading, + getObjectChildrenError, + } = useGetObjectChildren(idSystemObject, filter); - const { getObjectChildren, getObjectChildrenData, getObjectChildrenLoading, getObjectChildrenError } = useGetObjectChildren(idSystemObject, filter); + const queryData = getObjectChildrenData?.getObjectChildren; const nodeId = getRepositoryTreeNodeId(idSystemObject, idObject, objectType); - const isEmpty = !getObjectChildrenData?.getObjectChildren?.entries.length ?? false; - const entries = getSortedTreeEntries(getObjectChildrenData?.getObjectChildren?.entries ?? []); - const metadataColumns = getObjectChildrenData?.getObjectChildren?.metadataColumns ?? []; + const isEmpty = !queryData?.entries.length ?? true; + const entries = getSortedTreeEntries(queryData?.entries ?? []); + const metadataColumns = queryData?.metadataColumns ?? []; + const loading = getObjectChildrenLoading && !getObjectChildrenError; + + const loadData = expandedNodes.has(nodeId); + + React.useEffect(() => { + if (loadData) { + getObjectChildren(); + } + }, [loadData, getObjectChildren]); return ( - - {renderTreeNodes(filter, entries, metadataColumns)} + + {renderTreeNodes(expandedNodes, filter, entries, metadataColumns)} ); } -export const renderTreeNodes = (filter: RepositoryFilter, entries: NavigationResultEntry[], metadataColumns: eMetadata[]): React.ReactNode => +export const renderTreeNodes = (expandedNodes: ExpandedNodeMap, filter: RepositoryFilter, entries: NavigationResultEntry[], metadataColumns: eMetadata[]): React.ReactNode => entries.map((entry, index: number) => { const { idSystemObject, name, objectType, idObject, metadata } = entry; const variant = getTreeColorVariant(index); @@ -69,6 +84,7 @@ export const renderTreeNodes = (filter: RepositoryFilter, entries: NavigationRes idObject={idObject} treeColumns={treeColumns} filter={filter} + expandedNodes={expandedNodes} /> ); }); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index e3cc2b154..1f6c97c42 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -136,11 +136,11 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { return ( - - - {label} - - + + + {label} + + diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index cc2818663..1afbc30b3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -1,14 +1,15 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { TreeView } from '@material-ui/lab'; -import React from 'react'; +import lodash from 'lodash'; +import React, { useState } from 'react'; import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; import { Loader } from '../../../../components'; import { getSortedTreeEntries, getSystemObjectTypesForFilter, getTreeWidth } from '../../../../utils/repository'; import { useGetRootObjects } from '../../hooks/useRepository'; import { RepositoryFilter } from '../../index'; import RepositoryTreeHeader from './RepositoryTreeHeader'; -import { renderTreeNodes } from './RepositoryTreeNode'; +import { ExpandedNodeMap, renderTreeNodes } from './RepositoryTreeNode'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { @@ -45,6 +46,7 @@ interface RepositoryTreeViewProps { function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { const { filter } = props; const classes = useStyles(); + const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); const objectTypes = getSystemObjectTypesForFilter(filter); const { getRootObjectsData, getRootObjectsLoading, getRootObjectsError } = useGetRootObjects(objectTypes, filter); @@ -58,14 +60,26 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement let content: React.ReactNode = null; if (!getRootObjectsLoading && !getRootObjectsError) { - content = renderTreeNodes(filter, entries, metadataColumns); + content = renderTreeNodes(expandedNodes, filter, entries, metadataColumns); } else if (!noFilter) { content = ; } + const onNodeToggle = (_, nodeIds: string[]) => { + const keyValueArray: [string, undefined][] = lodash.map(nodeIds, (id: string) => [id, undefined]); + const updatedMap: ExpandedNodeMap = new Map(keyValueArray); + setExpandedNodes(updatedMap); + }; + return ( - } defaultExpandIcon={}> + } + defaultExpandIcon={} + > {noFilter && ( diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 439570abe..55e3db62f 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,7 +1,7 @@ -import { eSystemObjectType, eMetadata } from '../../../types/server'; -import { useQuery, useLazyQuery, ApolloError } from '@apollo/client'; +import { ApolloError, useLazyQuery, useQuery } from '@apollo/client'; +import { RepositoryFilter } from '../index'; import { GetObjectChildrenDocument, GetObjectChildrenQuery, GetObjectChildrenQueryVariables } from '../../../types/graphql'; -import { RepositoryFilter } from '..'; +import { eMetadata, eSystemObjectType } from '../../../types/server'; interface UseGetRootObjects { getRootObjectsData: GetObjectChildrenQuery | undefined; @@ -9,7 +9,6 @@ interface UseGetRootObjects { getRootObjectsError: ApolloError | undefined; } - function useGetRootObjects(objectTypes: eSystemObjectType[], filter: RepositoryFilter): UseGetRootObjects { const { data: getRootObjectsData, loading: getRootObjectsLoading, error: getRootObjectsError } = useQuery( GetObjectChildrenDocument, diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index b6523d23b..b5cff4d9f 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -31,7 +31,7 @@ function Repository(): React.ReactElement { const queries = parseRepositoryUrl(search); - const initialFilterState = { + const initialFilterState: RepositoryFilter = { units: true, projects: false };