Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: UILD-406: STORY: Read only screen in LDE #15

Merged
merged 1 commit into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ROUTES } from '@common/constants/routes.constants';
import { OKAPI_CONFIG } from '@common/constants/api.constants';
import { BASE_LOCALE, i18nMessages } from '@common/i18n/messages';
import { localStorageService } from '@common/services/storage';
import { Root, Search, Load, EditWrapper, ExternalResourceEdit } from '@views';
import { Root, Search, Load, EditWrapper, ExternalResourcePreview } from '@views';
import state from '@state';
import { ServicesProvider } from './providers';
import './App.scss';
Expand Down Expand Up @@ -43,8 +43,8 @@ export const routes: RouteObject[] = [
element: <Load />,
},
{
path: ROUTES.EXTERNAL_RESOURCE_EDIT.uri,
element: <ExternalResourceEdit />,
path: ROUTES.EXTERNAL_RESOURCE_PREVIEW.uri,
element: <ExternalResourcePreview />,
},
{
path: '*',
Expand Down
16 changes: 13 additions & 3 deletions src/common/api/records.api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { BIBFRAME_API_ENDPOINT, MAX_LIMIT } from '@common/constants/api.constants';
import {
BIBFRAME_API_ENDPOINT,
ExternalResourceIdType,
GET_RESOURCE_BY_TYPE_URIS,
MAX_LIMIT,
} from '@common/constants/api.constants';
import baseApi from './base.api';
import { TYPE_URIS } from '@common/constants/bibframe.constants';

type SingleRecord = {
recordId: string;
};

type IGetRecord = SingleRecord & {
idType?: ExternalResourceIdType;
};

type GetAllRecords = {
pageSize?: number;
pageNumber?: number;
Expand All @@ -14,8 +23,9 @@ type GetAllRecords = {

const singleRecordUrl = `${BIBFRAME_API_ENDPOINT}/:recordId`;

export const getRecord = async ({ recordId }: SingleRecord) => {
const url = baseApi.generateUrl(singleRecordUrl, { name: ':recordId', value: recordId });
export const getRecord = async ({ recordId, idType }: IGetRecord) => {
const selectedUrl = (idType && GET_RESOURCE_BY_TYPE_URIS[idType]) ?? singleRecordUrl;
const url = baseApi.generateUrl(selectedUrl, { name: ':recordId', value: recordId });

return baseApi.getJson({
url,
Expand Down
8 changes: 8 additions & 0 deletions src/common/constants/api.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ export const DEFAULT_PAGES_METADATA = {
totalElements: 0,
totalPages: 0,
};

export enum ExternalResourceIdType {
Inventory = 'inventory',
}

export const GET_RESOURCE_BY_TYPE_URIS = {
[ExternalResourceIdType.Inventory]: `${BIBFRAME_API_ENDPOINT}/preview/:recordId`,
};
7 changes: 4 additions & 3 deletions src/common/constants/routes.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ export const ROUTES = {
uri: '/resources/:resourceId/edit',
name: 'ld.editResource',
},
EXTERNAL_RESOURCE_EDIT: {
uri: '/resources/external/:resourceId/edit',
name: 'ld.externalResource',
EXTERNAL_RESOURCE_PREVIEW: {
uri: '/resources/external/:externalId/preview',
name: 'ld.externalResourcePreview',
},
};

export const RESOURCE_URLS = [ROUTES.RESOURCE_EDIT.uri];
export const EXTERNAL_RESOURCE_URLS = [ROUTES.EXTERNAL_RESOURCE_PREVIEW.uri];

export const RESOURCE_EDIT_CREATE_URLS = [ROUTES.RESOURCE_EDIT.uri, ROUTES.RESOURCE_CREATE.uri];

Expand Down
12 changes: 10 additions & 2 deletions src/common/helpers/record.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,18 @@ export const getSelectedRecordBlocks = (searchParams: URLSearchParams) => {
};

export const getRecordTitle = (record: RecordEntry) => {
const { block } = getEditingRecordBlocks(record);
// TODO: unify interactions with record and its format
// Some functions expect { resource: { %RECORD_CONTENTS% }}
// Others, like this one, expect { %RECORD_CONTENTS% }
const recordContents = unwrapRecordValuesFromCommonContainer(record);

const { block } = getEditingRecordBlocks(recordContents);

let selectedTitle;

TITLE_CONTAINER_URIS.every(uri => {
const selectedTitleContainer = (
record[block!]?.['http://bibfra.me/vocab/marc/title'] as unknown as Record<string, any>[]
recordContents[block!]?.['http://bibfra.me/vocab/marc/title'] as unknown as Record<string, any>[]
)?.find(obj => Object.hasOwn(obj, uri));

if (selectedTitleContainer) {
Expand All @@ -180,6 +185,9 @@ export const getAdjustedRecordContents = ({ record, block, reference, asClone }:
};
};

export const unwrapRecordValuesFromCommonContainer = (record: RecordEntry) =>
(record.resource ?? record) as RecordEntry;

export const wrapRecordValuesWithCommonContainer = (record: RecordEntry) => ({ resource: record });

export const checkIfRecordHasDependencies = (record: RecordEntry) => {
Expand Down
79 changes: 44 additions & 35 deletions src/common/hooks/useConfig.hook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react';
import { useContext, useRef } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import state from '@state';
Expand Down Expand Up @@ -33,6 +33,7 @@ export const useConfig = () => {
const setPreviewContent = useSetRecoilState(state.inputs.previewContent);
const setSelectedRecordBlocks = useSetRecoilState(state.inputs.selectedRecordBlocks);
const { getProcessedRecordAndSchema } = useProcessedRecordAndSchema();
const isProcessingProfiles = useRef(false);

const prepareFields = (profiles: ProfileEntry[]): ResourceTemplates => {
const preparedFields = profiles.reduce<ResourceTemplates>((fields, profile) => {
Expand Down Expand Up @@ -87,43 +88,51 @@ export const useConfig = () => {
};

const getProfiles = async ({ record, recordId, previewParams, asClone }: GetProfiles): Promise<any> => {
const hasStoredProfiles = profiles?.length;
const response = hasStoredProfiles ? profiles : await fetchProfiles();
// TODO: check a list of supported profiles and implement the profile selection
const selectedProfile = response.find(({ name }: ProfileEntry) => name === PROFILE_NAMES.MONOGRAPH);
const templates = preparedFields || prepareFields(response);

if (!hasStoredProfiles) {
setProfiles(response);
}
if (isProcessingProfiles.current && (record || recordId)) return;

try {
isProcessingProfiles.current = true;

setUserValues({});

const recordData = record?.resource || {};
const recordTitle = getRecordTitle(recordData as RecordEntry);
const entities = getPrimaryEntitiesFromRecord(record as RecordEntry);

if (selectedProfile) {
setSelectedProfile(selectedProfile);

const { updatedSchema, initKey } = await buildSchema(selectedProfile, templates, recordData, asClone);

if (previewParams && recordId) {
setPreviewContent(prev => [
...(previewParams.singular ? [] : prev.filter(({ id }) => id !== recordId)),
{
id: recordId,
base: updatedSchema,
userValues: userValuesService.getAllValues(),
initKey,
title: recordTitle,
entities,
},
]);
const hasStoredProfiles = profiles?.length;
const response = hasStoredProfiles ? profiles : await fetchProfiles();
// TODO: check a list of supported profiles and implement the profile selection
const selectedProfile = response.find(({ name }: ProfileEntry) => name === PROFILE_NAMES.MONOGRAPH);
const templates = preparedFields || prepareFields(response);

if (!hasStoredProfiles) {
setProfiles(response);
}
}

return response;
setUserValues({});

const recordData = record?.resource || {};
const recordTitle = getRecordTitle(recordData as RecordEntry);
const entities = getPrimaryEntitiesFromRecord(record as RecordEntry);

if (selectedProfile) {
setSelectedProfile(selectedProfile);

const { updatedSchema, initKey } = await buildSchema(selectedProfile, templates, recordData, asClone);

if (previewParams && recordId) {
setPreviewContent(prev => [
...(previewParams.singular ? [] : prev.filter(({ id }) => id !== recordId)),
{
id: recordId,
base: updatedSchema,
userValues: userValuesService.getAllValues(),
initKey,
title: recordTitle,
entities,
},
]);
}
}

return response;
} finally {
isProcessingProfiles.current = false;
}
};

return { getProfiles, prepareFields };
Expand Down
84 changes: 60 additions & 24 deletions src/common/hooks/useRecordControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,21 @@ import { generateEditResourceUrl } from '@common/helpers/navigation.helper';
import { useBackToSearchUri } from './useBackToSearchUri';
import state from '@state';
import { useContainerEvents } from './useContainerEvents';
import { ExternalResourceIdType } from '@common/constants/api.constants';

type SaveRecordProps = {
asRefToNewRecord?: boolean;
shouldSetSearchParams?: boolean;
isNavigatingBack?: boolean;
};

type IBaseFetchRecord = {
recordId?: string;
cachedRecord?: RecordEntry;
idType?: ExternalResourceIdType;
errorMessage?: string;
};

export const useRecordControls = () => {
const [searchParams, setSearchParams] = useSearchParams();
const setIsLoading = useSetRecoilState(state.loadingState.isLoading);
Expand Down Expand Up @@ -59,30 +67,30 @@ export const useRecordControls = () => {
const isClone = queryParams.get(QueryParams.CloneOf);

const fetchRecord = async (recordId: string, previewParams?: PreviewParams) => {
try {
const profile = PROFILE_BFIDS.MONOGRAPH;
const locallySavedData = getSavedRecord(profile, recordId);
const recordData: RecordEntry =
locallySavedData && !previewParams ? locallySavedData.data : await getRecord({ recordId });

if (!previewParams) {
setCurrentlyEditedEntityBfid(new Set(getPrimaryEntitiesFromRecord(recordData)));
setRecord(recordData);
}
const profile = PROFILE_BFIDS.MONOGRAPH;
const locallySavedData = getSavedRecord(profile, recordId);
const cachedRecord: RecordEntry | undefined =
locallySavedData && !previewParams ? (locallySavedData.data as RecordEntry) : undefined;

setCurrentlyPreviewedEntityBfid(new Set(getPrimaryEntitiesFromRecord(recordData, !!previewParams)));
const recordData = await getRecordAndInitializeParsing({ recordId, cachedRecord });

await getProfiles({ record: recordData, recordId, previewParams, asClone: Boolean(isClone) });
if (!recordData) return;

setIsEdited(false);
} catch (_err) {
console.error('Error fetching record.');

setStatusMessages(currentStatus => [
...currentStatus,
UserNotificationFactory.createMessage(StatusType.error, 'ld.errorFetching'),
]);
if (!previewParams) {
setCurrentlyEditedEntityBfid(new Set(getPrimaryEntitiesFromRecord(recordData)));
setRecord(recordData);
}

setCurrentlyPreviewedEntityBfid(new Set(getPrimaryEntitiesFromRecord(recordData, !!previewParams)));

await getProfiles({
record: recordData,
recordId,
previewParams,
asClone: Boolean(isClone),
});

setIsEdited(false);
};

const saveRecord = async ({
Expand Down Expand Up @@ -120,10 +128,7 @@ export const useRecordControls = () => {

setStatusMessages(currentStatus => [
...currentStatus,
UserNotificationFactory.createMessage(
StatusType.success,
recordId ? 'ld.rdUpdateSuccess' : 'ld.rdSaveSuccess',
),
UserNotificationFactory.createMessage(StatusType.success, recordId ? 'ld.rdUpdateSuccess' : 'ld.rdSaveSuccess'),
]);

// TODO: isEdited state update is not immediately reflected in the <Prompt />
Expand Down Expand Up @@ -261,6 +266,36 @@ export const useRecordControls = () => {
}
};

const getRecordAndInitializeParsing = async ({ recordId, cachedRecord, idType, errorMessage }: IBaseFetchRecord) => {
if (!recordId && !cachedRecord) return;

try {
const recordData: RecordEntry = cachedRecord ?? (recordId && (await getRecord({ recordId, idType })));

await getProfiles({
record: recordData,
recordId,
});

return recordData;
} catch (_err) {
setStatusMessages(currentStatus => [
...currentStatus,
UserNotificationFactory.createMessage(StatusType.error, errorMessage ?? 'ld.errorFetching'),
]);
}
};

const fetchExternalRecordForPreview = async (recordId?: string, idType = ExternalResourceIdType.Inventory) => {
if (!recordId) return;

await getRecordAndInitializeParsing({
recordId,
idType,
errorMessage: 'ld.errorFetchingExternalResourceForPreview',
});
};

return {
fetchRecord,
saveRecord,
Expand All @@ -269,5 +304,6 @@ export const useRecordControls = () => {
discardRecord,
clearRecordState,
fetchRecordAndSelectEntityValues,
fetchExternalRecordForPreview,
};
};
6 changes: 4 additions & 2 deletions src/common/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,14 @@ export const BASE_LOCALE = {
'ld.to': 'To',
'ld.apply': 'Apply',
'ld.notSpecified': 'Not specified',
'ld.externalResource': 'External resource',
'ld.externalResourcePreview': 'External resource preview',
'ld.errorFetchingExternalResourceForPreview': 'Error fetching external resource for preview',
'ld.fetchingExternalResourceById': 'Fetching external resource id {resourceId}...',
'ld.lastUpdated': 'Last updated',
'ld.marcAuthorityRecord': 'MARC authority record',
'ld.selectBrowseOption': 'Select a browse option',
'ld.searchQueryWouldBeHere': '{query} would be here'
'ld.searchQueryWouldBeHere': '{query} would be here',
'ld.continue': 'Continue',
};

export const i18nMessages = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { useParams } from 'react-router-dom';
import './ExternalResourceLoader.scss';

export const ExternalResourceLoader = () => {
const { resourceId } = useParams();
const { externalId } = useParams();

return (
<div className="external-resource-loader">
<div className="contents">
<FormattedMessage id="ld.fetchingExternalResourceById" values={{ resourceId }} />
<FormattedMessage id="ld.fetchingExternalResourceById" values={{ resourceId: externalId }} />
</div>
</div>
);
Expand Down
Loading
Loading