diff --git a/src/App.scss b/src/App.scss index 4ac8852a..fb3867bf 100644 --- a/src/App.scss +++ b/src/App.scss @@ -16,6 +16,14 @@ body > #editor-root, // font-size: 16px; } +h2, +h3, +h4, +h5, +h6 { + margin: 0; +} + #app-root { display: flex; flex-direction: column; diff --git a/src/common/constants/bibframeMapping.constants.ts b/src/common/constants/bibframeMapping.constants.ts index f226a839..65bad2fc 100644 --- a/src/common/constants/bibframeMapping.constants.ts +++ b/src/common/constants/bibframeMapping.constants.ts @@ -24,6 +24,7 @@ export const BFLITE_URIS = { CLASSIFICATION: 'http://bibfra.me/vocab/lite/classification', PROVISION_ACTIVITY: 'https://bibfra.me/vocab/marc/provisionActivity', TITLE: 'http://bibfra.me/vocab/marc/title', + TITLE_CONTAINER: 'http://bibfra.me/vocab/marc/Title', MAIN_TITLE: 'http://bibfra.me/vocab/marc/mainTitle', PRODUCTION: 'http://bibfra.me/vocab/marc/production', PUBLICATION: 'http://bibfra.me/vocab/marc/publication', @@ -215,29 +216,37 @@ export const BF_URIS = { LABEL: 'http://www.w3.org/2000/01/rdf-schema#label', }; +export const INSTANCE_REF_KEY = "_instanceReference" +export const WORK_REF_KEY = "_workReference" + export const BLOCKS_BFLITE = { INSTANCE: { uri: BFLITE_URIS.INSTANCE, - referenceKey: '_instanceReference', + referenceKey: INSTANCE_REF_KEY, resourceType: ResourceType.instance, reference: { - key: '_workReference', + key: WORK_REF_KEY, uri: BFLITE_URIS.WORK, name: ResourceType.work, }, }, WORK: { uri: BFLITE_URIS.WORK, - referenceKey: '_workReference', + referenceKey: WORK_REF_KEY, resourceType: ResourceType.work, reference: { - key: '_instanceReference', + key: INSTANCE_REF_KEY, uri: BFLITE_URIS.INSTANCE, name: ResourceType.instance, }, }, }; +export const REF_TO_NAME = { + [INSTANCE_REF_KEY]: ResourceType.instance, + [WORK_REF_KEY]: ResourceType.work, +} + export const BFLITE_BFID_TO_BLOCK = { 'lc:RT:bf2:Monograph:Instance': BLOCKS_BFLITE.INSTANCE, 'lc:RT:bf2:Monograph:Work': BLOCKS_BFLITE.WORK, @@ -609,6 +618,9 @@ export const TYPE_MAP = { export const NEW_BF2_TO_BFLITE_MAPPING = { [BFLITE_URIS.INSTANCE]: { + [BFLITE_URIS.INSTANCE]: { + container: { bf2Uri: 'http://id.loc.gov/ontologies/bibframe/Instance' } + }, 'http://bibfra.me/vocab/marc/title': { container: { bf2Uri: 'http://id.loc.gov/ontologies/bibframe/title' }, options: { diff --git a/src/common/helpers/record.helper.ts b/src/common/helpers/record.helper.ts index 177a1ed0..febbf21b 100644 --- a/src/common/helpers/record.helper.ts +++ b/src/common/helpers/record.helper.ts @@ -10,7 +10,12 @@ import { TYPE_URIS, } from '@common/constants/bibframe.constants'; import { formatRecord } from './recordFormatting.helper'; -import { BFLITE_URI_TO_BLOCK, BFLITE_URIS, BLOCKS_BFLITE } from '@common/constants/bibframeMapping.constants'; +import { + BFLITE_URI_TO_BLOCK, + BFLITE_URIS, + BLOCKS_BFLITE, + REF_TO_NAME, +} from '@common/constants/bibframeMapping.constants'; import { ResourceType } from '@common/constants/record.constants'; import { QueryParams } from '@common/constants/routes.constants'; import { cloneDeep } from 'lodash'; @@ -217,6 +222,7 @@ export const getPreviewFieldsConditions = ({ isOnBranchWithUserValue, altDisplayNames, hideActions, + hideEntities, isEntity, forceRenderAllTopLevelEntities, }: { @@ -228,6 +234,7 @@ export const getPreviewFieldsConditions = ({ isOnBranchWithUserValue: boolean; altDisplayNames?: Record; hideActions?: boolean; + hideEntities?: boolean; isEntity: boolean; forceRenderAllTopLevelEntities?: boolean; }) => { @@ -241,7 +248,7 @@ export const getPreviewFieldsConditions = ({ const isBranchEndWithoutValues = !selectedUserValues && isBranchEnd; const isBranchEndWithValues = !!selectedUserValues; const shouldRenderLabelOrPlaceholders = - (isPreviewable && isGroupable) || + (!(isEntity && hideEntities) && isPreviewable && isGroupable) || type === AdvancedFieldType.dropdown || (isBranchEndWithValues && type !== AdvancedFieldType.complex) || isBranchEndWithoutValues; @@ -274,3 +281,18 @@ export const getPreviewFieldsConditions = ({ wrapEntities, }; }; + +export const getRecordDependencies = (record?: RecordEntry | null) => { + if (!record) return; + + const contents = unwrapRecordValuesFromCommonContainer(record); + const { block, reference } = getEditingRecordBlocks(contents); + + if (block && reference) { + return { + keys: reference, + type: REF_TO_NAME[reference.key as keyof typeof REF_TO_NAME], + entries: contents[block][reference.key] as unknown as RecursiveRecordSchema[], + }; + } +}; diff --git a/src/common/helpers/recordFormatting.helper.ts b/src/common/helpers/recordFormatting.helper.ts index fec66d12..fc5f9435 100644 --- a/src/common/helpers/recordFormatting.helper.ts +++ b/src/common/helpers/recordFormatting.helper.ts @@ -7,6 +7,7 @@ import { NON_BF_RECORD_ELEMENTS, } from '@common/constants/bibframeMapping.constants'; import { getRecordPropertyData } from './record.helper'; +import { Row } from '@components/Table'; export const formatRecord = ({ parsedRecord, @@ -232,3 +233,36 @@ export const applyIntlToTemplates = ({ ...rest, template: Object.fromEntries(Object.entries(template).map(([k, v]) => [k, format({ id: v })])), })); + +export const formatDependeciesTable = (deps: Record[]): Row[] => { + return deps.map(({ id, ...rest }) => { + const selectedPublication = (rest?.[BFLITE_URIS.PUBLICATION] as Record)?.[0] as Record< + string, + unknown[] + >; + const selectedTitle = (rest?.[BFLITE_URIS.TITLE] as Record)?.[0] as Record< + string, + Record + >; + + return { + __meta: { + id, + key: id, + ...rest, + }, + title: { + label: selectedTitle?.[BFLITE_URIS.TITLE_CONTAINER]?.[BFLITE_URIS.MAIN_TITLE]?.[0], + className: 'title', + }, + publisher: { + label: selectedPublication?.[BFLITE_URIS.NAME]?.[0], + className: 'publisher', + }, + pubDate: { + label: selectedPublication?.[BFLITE_URIS.DATE]?.[0], + className: 'publication-date', + }, + }; + }) as Row[]; +}; diff --git a/src/common/hooks/useConfig.hook.ts b/src/common/hooks/useConfig.hook.ts index 34b94eb3..97c40cf3 100644 --- a/src/common/hooks/useConfig.hook.ts +++ b/src/common/hooks/useConfig.hook.ts @@ -9,16 +9,25 @@ import { useProcessedRecordAndSchema } from './useProcessedRecordAndSchema.hook' import { useServicesContext } from './useServicesContext'; export type PreviewParams = { + noStateUpdate?: boolean; singular?: boolean; }; -type GetProfiles = { +type IGetProfiles = { record?: RecordEntry; recordId?: string; previewParams?: PreviewParams; asClone?: boolean; }; +type IBuildSchema = { + profile: ProfileEntry; + templates: ResourceTemplates; + record: Record | Array; + asClone?: boolean; + noStateUpdate?: boolean; +}; + export const useConfig = () => { const { schemaCreatorService, userValuesService, selectedEntriesService } = useServicesContext() as Required; @@ -56,12 +65,7 @@ export const useConfig = () => { return preparedFields; }; - const buildSchema = async ( - profile: ProfileEntry, - templates: ResourceTemplates, - record: Record | Array, - asClone = false, - ) => { + const buildSchema = async ({ profile, templates, record, asClone = false, noStateUpdate = false }: IBuildSchema) => { const initKey = uuidv4(); const userValues: UserValues = {}; @@ -75,18 +79,21 @@ export const useConfig = () => { record, userValues, asClone, + noStateUpdate, }); - setUserValues(updatedUserValues || userValues); - setInitialSchemaKey(initKey); - setSelectedEntries(selectedEntriesService.get()); - setSchema(updatedSchema); - setSelectedRecordBlocks(selectedRecordBlocks); + if (!noStateUpdate) { + setUserValues(updatedUserValues || userValues); + setInitialSchemaKey(initKey); + setSelectedEntries(selectedEntriesService.get()); + setSchema(updatedSchema); + setSelectedRecordBlocks(selectedRecordBlocks); + } return { updatedSchema, initKey }; }; - const getProfiles = async ({ record, recordId, previewParams, asClone }: GetProfiles): Promise => { + const getProfiles = async ({ record, recordId, previewParams, asClone }: IGetProfiles): Promise => { if (isProcessingProfiles.current && (record || recordId)) return; try { @@ -102,16 +109,20 @@ export const useConfig = () => { setProfiles(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); + !previewParams?.noStateUpdate && setSelectedProfile(selectedProfile); + + const { updatedSchema, initKey } = await buildSchema({ + profile: selectedProfile, + templates, + record: recordData, + asClone, + noStateUpdate: previewParams?.noStateUpdate, + }); if (previewParams && recordId) { setPreviewContent(prev => [ diff --git a/src/common/hooks/useProcessedRecordAndSchema.hook.ts b/src/common/hooks/useProcessedRecordAndSchema.hook.ts index 7eb5c183..b817a5a8 100644 --- a/src/common/hooks/useProcessedRecordAndSchema.hook.ts +++ b/src/common/hooks/useProcessedRecordAndSchema.hook.ts @@ -18,6 +18,7 @@ type IGetProcessedRecordAndSchema = { record: Record | Array; userValues: UserValues; asClone?: boolean; + noStateUpdate?: boolean; }; export const useProcessedRecordAndSchema = () => { @@ -28,7 +29,7 @@ export const useProcessedRecordAndSchema = () => { useServicesContext() as Required; const getProcessedRecordAndSchema = useCallback( - async ({ baseSchema, record, userValues, asClone = false }: IGetProcessedRecordAndSchema) => { + async ({ baseSchema, record, userValues, asClone = false, noStateUpdate }: IGetProcessedRecordAndSchema) => { let updatedSchema = baseSchema; let updatedUserValues = userValues; let selectedRecordBlocks = undefined; @@ -52,7 +53,7 @@ export const useProcessedRecordAndSchema = () => { }) : undefined; - setRecord(wrapRecordValuesWithCommonContainer(adjustedRecord)); + !noStateUpdate && setRecord(wrapRecordValuesWithCommonContainer(adjustedRecord)); selectedRecordBlocks = { block, reference }; schemaWithDuplicatesService.set(baseSchema); recordNormalizingService.init(adjustedRecord, block, reference); diff --git a/src/common/hooks/useRecordControls.ts b/src/common/hooks/useRecordControls.ts index 4b9b7821..1b440858 100644 --- a/src/common/hooks/useRecordControls.ts +++ b/src/common/hooks/useRecordControls.ts @@ -44,6 +44,7 @@ type IBaseFetchRecord = { cachedRecord?: RecordEntry; idType?: ExternalResourceIdType; errorMessage?: string; + previewParams?: PreviewParams; }; export const useRecordControls = () => { @@ -270,7 +271,7 @@ export const useRecordControls = () => { } }; - const getRecordAndInitializeParsing = async ({ recordId, cachedRecord, idType, errorMessage }: IBaseFetchRecord) => { + const getRecordAndInitializeParsing = async ({ recordId, cachedRecord, idType, previewParams, errorMessage }: IBaseFetchRecord) => { if (!recordId && !cachedRecord) return; try { @@ -279,6 +280,7 @@ export const useRecordControls = () => { await getProfiles({ record: recordData, recordId, + previewParams, }); return recordData; @@ -333,5 +335,6 @@ export const useRecordControls = () => { fetchRecordAndSelectEntityValues, fetchExternalRecordForPreview, tryFetchExternalRecordForEdit, + getRecordAndInitializeParsing, }; }; diff --git a/src/common/services/recordToSchemaMapping/recordToSchemaMapping.service.ts b/src/common/services/recordToSchemaMapping/recordToSchemaMapping.service.ts index 885e409f..68c0621d 100644 --- a/src/common/services/recordToSchemaMapping/recordToSchemaMapping.service.ts +++ b/src/common/services/recordToSchemaMapping/recordToSchemaMapping.service.ts @@ -176,8 +176,8 @@ export class RecordToSchemaMappingService implements IRecordToSchemaMapping { private readonly getSchemaEntries = (containerBf2Uri?: string, containerDataTypeUri?: string) => { return this.schemaArray.filter((entry: SchemaEntry) => { - const hasTheSameUri = entry.uri === containerBf2Uri; - const hasTheSameDataTypeUri = containerDataTypeUri + const isOfSameUri = entry.uri === containerBf2Uri; + const isOfSameDataTypeUri = containerDataTypeUri ? entry.constraints?.valueDataType?.dataTypeURI === containerDataTypeUri : true; let hasBlockParent = false; @@ -195,8 +195,7 @@ export class RecordToSchemaMappingService implements IRecordToSchemaMapping { if (this.updatedSchema?.get(getParentEntryUuid(entry.path))?.type === AdvancedFieldTypeEnum.block) { hasBlockParent = true; } - - return hasTheSameUri && hasTheSameDataTypeUri && hasProperBlock && hasBlockParent; + return isOfSameUri && isOfSameDataTypeUri && hasProperBlock && hasBlockParent; }); }; diff --git a/src/components/EditPreview/EditPreview.tsx b/src/components/EditPreview/EditPreview.tsx index a3ad6545..0fcc8ce4 100644 --- a/src/components/EditPreview/EditPreview.tsx +++ b/src/components/EditPreview/EditPreview.tsx @@ -1,9 +1,8 @@ -import { Preview } from '@components/Preview'; import { Button, ButtonType } from '@components/Button'; import { PROFILE_BFIDS } from '@common/constants/bibframe.constants'; import state from '@state'; import classNames from 'classnames'; -import { useRecoilValue } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { FormattedMessage } from 'react-intl'; import { useParams, useSearchParams } from 'react-router-dom'; import { QueryParams, RESOURCE_CREATE_URLS, ROUTES } from '@common/constants/routes.constants'; @@ -11,23 +10,27 @@ import { ResourceType } from '@common/constants/record.constants'; import { InstancesList } from '@components/InstancesList'; import { useRoutePathPattern } from '@common/hooks/useRoutePathPattern'; import { useNavigateToEditPage } from '@common/hooks/useNavigateToEditPage'; -import { checkIfRecordHasDependencies } from '@common/helpers/record.helper'; +import { getRecordDependencies } from '@common/helpers/record.helper'; +import { memo } from 'react'; +import { TitledPreview } from '@components/Preview/TitledPreview'; import './EditPreview.scss'; -export const EditPreview = () => { +export const EditPreview = memo(() => { const currentlyPreviewedEntityBfid = useRecoilValue(state.ui.currentlyPreviewedEntityBfid); const isEdited = useRecoilValue(state.status.recordIsEdited); const record = useRecoilValue(state.inputs.record); - const isPositionedSecond = - currentlyPreviewedEntityBfid.has(PROFILE_BFIDS.INSTANCE) && currentlyPreviewedEntityBfid.values.length <= 1; - const { resourceId } = useParams(); + const [previewContent, setPreviewContent] = useRecoilState(state.inputs.previewContent); const isCreatePageOpen = useRoutePathPattern(RESOURCE_CREATE_URLS); + const { resourceId } = useParams(); + const { navigateToEditPage } = useNavigateToEditPage(); const [queryParams] = useSearchParams(); + const isPositionedSecond = + currentlyPreviewedEntityBfid.has(PROFILE_BFIDS.INSTANCE) && currentlyPreviewedEntityBfid.values.length <= 1; const typeParam = queryParams.get(QueryParams.Type); const isCreateWorkPageOpened = isCreatePageOpen && typeParam === ResourceType.work; - const recordHasDependencies = checkIfRecordHasDependencies(record as RecordEntry); - const showPreview = recordHasDependencies && !isCreateWorkPageOpened; - const { navigateToEditPage } = useNavigateToEditPage(); + const dependencies = getRecordDependencies(record); + const showPreview = (dependencies?.entries?.length === 1 && !isCreateWorkPageOpened) || previewContent.length; + const selectedForPreview = previewContent?.[0]; return (
{ 'positioned-second': isPositionedSecond, })} > - {currentlyPreviewedEntityBfid.has(PROFILE_BFIDS.INSTANCE) && ( + {currentlyPreviewedEntityBfid.has(PROFILE_BFIDS.INSTANCE) && !dependencies && (
- {!recordHasDependencies && ( - - )} +
)} - {showPreview ? : } + {showPreview ? ( + setPreviewContent(prev => prev.filter(({ id }) => id !== selectedForPreview.id))} + showCloseCtl={(dependencies?.entries?.length ?? 0) > 1} + /> + ) : ( + + )}
); -}; +}); diff --git a/src/components/EditSection/EditSection.scss b/src/components/EditSection/EditSection.scss index feac4e01..e921d34d 100644 --- a/src/components/EditSection/EditSection.scss +++ b/src/components/EditSection/EditSection.scss @@ -5,6 +5,7 @@ flex-direction: column; gap: 0.313rem; width: 100%; + padding: 0.75rem 0.938rem; .input-group { display: flex; diff --git a/src/components/FullDisplay/PreviewContent.tsx b/src/components/FullDisplay/PreviewContent.tsx index 3c3fcc9f..4841db80 100644 --- a/src/components/FullDisplay/PreviewContent.tsx +++ b/src/components/FullDisplay/PreviewContent.tsx @@ -38,7 +38,7 @@ export const PreviewContent = () => { {Object.keys(userValues).length ? (
- +
) : (
diff --git a/src/components/InstancesList/InstancesList.scss b/src/components/InstancesList/InstancesList.scss index c4ad7f6b..75498fe8 100644 --- a/src/components/InstancesList/InstancesList.scss +++ b/src/components/InstancesList/InstancesList.scss @@ -1,6 +1,45 @@ -.instances-list-empty { - margin-top: 99px; - font-size: 1.125rem; - text-align: center; - color: rgba(0, 0, 0, 0.62); +.instances-list { + padding: 0.75rem 0.938rem; + + &-header { + display: flex; + justify-content: space-between; + padding-bottom: 0.5rem; + } + + h3 { + font-size: 1.138rem; + } + + .no-instances { + font-size: 1.125rem; + color: rgba(0, 0, 0, 0.62); + text-align: center; + padding: 2.5rem 0; + } + + table { + th { + background-color: transparent; + font-weight: normal; + } + + tr { + padding: 0.5rem 0 + } + + .button-primary { + margin: 0.2rem 0; + } + + .edit-ctl { + width: 0.5rem; + } + + td { + > * { + font-weight: normal; + } + } + } } diff --git a/src/components/InstancesList/InstancesList.tsx b/src/components/InstancesList/InstancesList.tsx index 681a6794..e7abd4ff 100644 --- a/src/components/InstancesList/InstancesList.tsx +++ b/src/components/InstancesList/InstancesList.tsx @@ -1,13 +1,105 @@ import { FormattedMessage } from 'react-intl'; +import { FC } from 'react'; +import { Row, Table } from '@components/Table'; +import { Button, ButtonType } from '@components/Button'; +import { formatDependeciesTable } from '@common/helpers/recordFormatting.helper'; +import { useRecordControls } from '@common/hooks/useRecordControls'; +import { useNavigateToEditPage } from '@common/hooks/useNavigateToEditPage'; +import { generateEditResourceUrl } from '@common/helpers/navigation.helper'; +import { wrapRecordValuesWithCommonContainer } from '@common/helpers/record.helper'; +import { ROUTES, QueryParams } from '@common/constants/routes.constants'; import './InstancesList.scss'; -export const InstancesList = () => { +type IInstancesList = { + contents?: { keys: { key?: string; uri?: string }; entries: Record[] }; + type?: string; + refId?: string; +}; + +const instancesListHeader: Row = { + title: { + label: , + position: 0, + }, + publisher: { + label: , + position: 1, + }, + pubDate: { + label: , + position: 2, + }, + editCtl: { + position: 3, + }, +}; + +export const InstancesList: FC = ({ contents: { keys, entries } = {}, type, refId }) => { + const { getRecordAndInitializeParsing } = useRecordControls(); + const { navigateToEditPage } = useNavigateToEditPage(); + + const onClickNewInstance = () => + type && + refId && + navigateToEditPage(`${ROUTES.RESOURCE_CREATE.uri}?${QueryParams.Type}=${type}&${QueryParams.Ref}=${refId}`); + + const applyActionItems = (rows: Row[]): Row[] => + rows.map(row => { + const onClickPreview = () => + keys?.uri && + getRecordAndInitializeParsing({ + recordId: row.__meta.id, + cachedRecord: wrapRecordValuesWithCommonContainer({ [keys?.uri]: row.__meta }), + previewParams: { singular: true, noStateUpdate: true }, + }); + + const onClickEdit = () => navigateToEditPage(generateEditResourceUrl(row.__meta?.id)); + + return { + ...row, + title: { + ...row.title, + children: ( + + ), + }, + editCtl: { + className: 'edit-ctl', + children: ( + + ), + }, + }; + }); + + const formattedInstances = applyActionItems(formatDependeciesTable(entries ?? [])); + return (
- {/* Show this only when there is no Instances */} -
- +
+

+ +

+
+ {entries?.length ? ( + + ) : ( +
+ +
+ )} ); }; diff --git a/src/components/Preview/Fields.tsx b/src/components/Preview/Fields.tsx index 69d04294..001ec5c9 100644 --- a/src/components/Preview/Fields.tsx +++ b/src/components/Preview/Fields.tsx @@ -1,14 +1,10 @@ import { ReactNode } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import classNames from 'classnames'; import { ENTITY_LEVEL, GROUP_BY_LEVEL } from '@common/constants/bibframe.constants'; -import { BFLITE_BFID_TO_BLOCK } from '@common/constants/bibframeMapping.constants'; -import { RecordStatus } from '@common/constants/record.constants'; import { AdvancedFieldType } from '@common/constants/uiControls.constants'; -import { generateEditResourceUrl } from '@common/helpers/navigation.helper'; -import { getRecordId, getPreviewFieldsConditions } from '@common/helpers/record.helper'; +import { getPreviewFieldsConditions } from '@common/helpers/record.helper'; import { getParentEntryUuid } from '@common/helpers/schema.helper'; -import { useNavigateToEditPage } from '@common/hooks/useNavigateToEditPage'; import { ConditionalWrapper } from '@components/ConditionalWrapper'; import state from '@state'; import { Labels } from './Labels'; @@ -61,6 +57,7 @@ type FieldsProps = { altUserValues?: UserValues; altDisplayNames?: Record; hideActions?: boolean; + hideEntities?: boolean; forceRenderAllTopLevelEntities?: boolean; }; @@ -73,28 +70,16 @@ export const Fields = ({ altUserValues, altDisplayNames, hideActions, + hideEntities, forceRenderAllTopLevelEntities, }: FieldsProps) => { const userValuesFromState = useRecoilValue(state.inputs.userValues); const schemaFromState = useRecoilValue(state.config.schema); const selectedEntries = useRecoilValue(state.config.selectedEntries); - const setRecordStatus = useSetRecoilState(state.status.recordStatus); - const record = useRecoilValue(state.inputs.record); const currentlyPreviewedEntityBfid = useRecoilValue(state.ui.currentlyPreviewedEntityBfid); - const isEdited = useRecoilValue(state.status.recordIsEdited); - const { navigateToEditPage } = useNavigateToEditPage(); const userValues = altUserValues || userValuesFromState; const schema = altSchema || schemaFromState; - const handleNavigateToEditPage = () => { - setRecordStatus({ type: isEdited ? RecordStatus.saveAndClose : RecordStatus.close }); - - const typedSelectedBlock = BFLITE_BFID_TO_BLOCK[bfid as keyof typeof BFLITE_BFID_TO_BLOCK]; - const id = getRecordId(record, typedSelectedBlock.reference.uri, typedSelectedBlock.referenceKey); - - navigateToEditPage(generateEditResourceUrl(id)); - }; - const entry = base.get(uuid); const isOnBranchWithUserValue = paths.includes(uuid); const isEntity = level === ENTITY_LEVEL; @@ -125,7 +110,6 @@ export const Fields = ({ isBlock, isBlockContents, isInstance, - showEntityActions, wrapEntities, } = getPreviewFieldsConditions({ entry, @@ -136,6 +120,7 @@ export const Fields = ({ isOnBranchWithUserValue, altDisplayNames, hideActions, + hideEntities, isEntity, forceRenderAllTopLevelEntities, }); @@ -153,10 +138,6 @@ export const Fields = ({ isInstance={isInstance} altDisplayNames={altDisplayNames} displayNameWithAltValue={displayNameWithAltValue} - showEntityActions={showEntityActions} - bfid={bfid} - handleNavigateToEditPage={handleNavigateToEditPage} - record={record} /> )} {shouldRenderValuesOrPlaceholders && ( @@ -187,6 +168,7 @@ export const Fields = ({ altUserValues={altUserValues} altDisplayNames={altDisplayNames} hideActions={hideActions} + hideEntities={hideEntities} forceRenderAllTopLevelEntities={forceRenderAllTopLevelEntities} /> diff --git a/src/components/Preview/Labels.tsx b/src/components/Preview/Labels.tsx index e1775554..d37ded2a 100644 --- a/src/components/Preview/Labels.tsx +++ b/src/components/Preview/Labels.tsx @@ -1,12 +1,6 @@ import { FC } from 'react'; -import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Lightbulb16 from '@src/assets/lightbulb-shining-16.svg?react'; -import { RESOURCE_TEMPLATE_IDS } from '@common/constants/bibframe.constants'; -import { BLOCKS_BFLITE } from '@common/constants/bibframeMapping.constants'; -import { getRecordId } from '@common/helpers/record.helper'; -import { Button, ButtonType } from '@components/Button'; -import { PreviewActionsDropdown } from '@components/PreviewActionsDropdown'; type LabelProps = { isEntity: boolean; @@ -15,10 +9,6 @@ type LabelProps = { isInstance: boolean; altDisplayNames?: Record; displayNameWithAltValue: string; - showEntityActions: boolean; - bfid: string; - handleNavigateToEditPage: VoidFunction; - record: RecordEntry | null; }; export const Labels: FC = ({ @@ -28,10 +18,6 @@ export const Labels: FC = ({ isInstance, altDisplayNames, displayNameWithAltValue, - showEntityActions, - bfid, - handleNavigateToEditPage, - record, }) => { return ( = ({ > {isEntity && !isInstance && !altDisplayNames && } {displayNameWithAltValue} - {showEntityActions && !isInstance && ( - - )} - {showEntityActions && isInstance && ( - - )} ); }; diff --git a/src/components/Preview/Preview.scss b/src/components/Preview/Preview.scss index 24c59026..e8329904 100644 --- a/src/components/Preview/Preview.scss +++ b/src/components/Preview/Preview.scss @@ -1,4 +1,6 @@ .preview-panel { + padding: 0 0.938rem 0.75rem 0.938rem; + .block { border: 1px solid red; min-height: 0.5rem; @@ -81,3 +83,32 @@ flex-direction: row; justify-content: center; } + +.titled-preview { + padding: 0; + + &-header { + padding: 0.2rem 0.938rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + background-color: rgba(0, 0, 0, 0.05); + + &-plain { + padding-top: 0.75rem; + border-bottom: none; + background-color: transparent; + } + + .details { + display: flex; + flex-direction: column; + align-items: center; + + span { + color: rgba(0, 0, 0, 0.6) + } + } + } +} diff --git a/src/components/Preview/Preview.tsx b/src/components/Preview/Preview.tsx index d03b19b9..6454601a 100644 --- a/src/components/Preview/Preview.tsx +++ b/src/components/Preview/Preview.tsx @@ -1,6 +1,5 @@ import { FC } from 'react'; import { useRecoilValue } from 'recoil'; -import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import state from '@state'; import { Fields } from './Fields'; @@ -11,8 +10,8 @@ type IPreview = { altUserValues?: UserValues; altInitKey?: string; altDisplayNames?: Record; - headless?: boolean; hideActions?: boolean; + hideEntities?: boolean; forceRenderAllTopLevelEntities?: boolean; entityRowDisplay?: boolean; }; @@ -22,8 +21,8 @@ export const Preview: FC = ({ altUserValues, altInitKey, altDisplayNames, - headless = false, hideActions, + hideEntities, forceRenderAllTopLevelEntities, entityRowDisplay, }) => { @@ -39,11 +38,6 @@ export const Preview: FC = ({ className={classNames('preview-panel', { 'preview-panel-row': entityRowDisplay })} data-testid="preview-fields" > - {!headless && ( -

- -

- )} {initialSchemaKey && ( = ({ altUserValues={altUserValues} altDisplayNames={altDisplayNames} hideActions={hideActions} + hideEntities={hideEntities} forceRenderAllTopLevelEntities={forceRenderAllTopLevelEntities} /> )} diff --git a/src/components/Preview/TitledPreview.tsx b/src/components/Preview/TitledPreview.tsx new file mode 100644 index 00000000..6a56a719 --- /dev/null +++ b/src/components/Preview/TitledPreview.tsx @@ -0,0 +1,81 @@ +import { Button, ButtonType } from '@components/Button'; +import { Preview } from './Preview'; +import { PreviewActionsDropdown } from '@components/PreviewActionsDropdown'; +import Times16 from '@src/assets/times-16.svg?react'; +import { FormattedMessage } from 'react-intl'; +import { ResourceType } from '@common/constants/record.constants'; +import { useNavigateToEditPage } from '@common/hooks/useNavigateToEditPage'; +import { generateEditResourceUrl } from '@common/helpers/navigation.helper'; +import classNames from 'classnames'; + +type ITitledPreview = { + showCloseCtl?: boolean; + ownId?: string; + refId?: string | null; + type?: string; + previewContent?: PreviewContent; + onClickClose?: VoidFunction; +}; + +export const TitledPreview = ({ + showCloseCtl = true, + ownId, + refId, + type, + previewContent, + onClickClose, +}: ITitledPreview) => { + const { navigateToEditPage } = useNavigateToEditPage(); + const { title, id, base, initKey, userValues } = previewContent ?? {}; + const selectedOwnId = id ?? ownId; + const withPreviewContent = ( + <> + {showCloseCtl ? ( + + ) : ( + + )} +
+ {title &&
{title}
} + {type && ( + + + + )} +
+ + ); + + const navigateToOwnEditPage = () => selectedOwnId && navigateToEditPage(generateEditResourceUrl(selectedOwnId)); + const navigateToRefEditPage = () => refId && navigateToEditPage(generateEditResourceUrl(refId)); + + return ( +
+
+ {previewContent ? ( + withPreviewContent + ) : ( +

+ +

+ )} + {type === ResourceType.work && !previewContent && ( + + )} + {type === ResourceType.instance && ( + + )} +
+ +
+ ); +}; diff --git a/src/components/PreviewActionsDropdown/PreviewActionsDropdown.tsx b/src/components/PreviewActionsDropdown/PreviewActionsDropdown.tsx index 1a265239..bbf86a52 100644 --- a/src/components/PreviewActionsDropdown/PreviewActionsDropdown.tsx +++ b/src/components/PreviewActionsDropdown/PreviewActionsDropdown.tsx @@ -8,7 +8,7 @@ import { FC } from 'react'; import { QueryParams, ROUTES } from '@common/constants/routes.constants'; type Props = { - referenceId?: string; + referenceId?: string | null; entityType?: string; ownId?: string; handleNavigateToEditPage?: VoidFunction; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index e6cab131..a02d03a0 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -21,7 +21,7 @@ export type Table = { }; export const Table = ({ header, data, className, onRowClick, onHeaderCellClick, selectedRows }: Table) => { - const sortedHeaderEntries = Object.entries(header).sort( + const sortedHeaderEntries = Object.entries(header).toSorted( ([_key1, value1], [_key2, value2]) => (value1?.position ?? 0) - (value2?.position ?? 0), ); diff --git a/src/test/__tests__/components/EditPreview.test.tsx b/src/test/__tests__/components/EditPreview.test.tsx index b3fb0863..fa619127 100644 --- a/src/test/__tests__/components/EditPreview.test.tsx +++ b/src/test/__tests__/components/EditPreview.test.tsx @@ -5,6 +5,8 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { RecoilRoot } from 'recoil'; +jest.mock('@common/constants/build.constants', () => ({ IS_EMBEDDED_MODE: true })); + const navigate = jest.fn(); jest.mock('react-router-dom', () => ({ diff --git a/src/views/Edit/Edit.scss b/src/views/Edit/Edit.scss index 222324e4..0f52c7ea 100644 --- a/src/views/Edit/Edit.scss +++ b/src/views/Edit/Edit.scss @@ -7,6 +7,5 @@ > *:not(.view-marc-modal) { overflow: auto; - padding: 0.75rem 0.938rem; } } diff --git a/src/views/ExternalResource/ExternalResourcePreview.tsx b/src/views/ExternalResource/ExternalResourcePreview.tsx index 5741f991..d5c2d624 100644 --- a/src/views/ExternalResource/ExternalResourcePreview.tsx +++ b/src/views/ExternalResource/ExternalResourcePreview.tsx @@ -26,7 +26,6 @@ export const ExternalResourcePreview = () => { diff --git a/translations/ui-linked-data/en.json b/translations/ui-linked-data/en.json index 53038711..1624e6db 100644 --- a/translations/ui-linked-data/en.json +++ b/translations/ui-linked-data/en.json @@ -90,6 +90,8 @@ "ld.classificationNumber": "Classification number", "ld.work": "Work", "ld.instances": "Instances", + "ld.instance": "Instance", + "ld.year": "Year", "ld.noInstancesAvailable": "No instances available.", "ld.noInstancesAdded": "No instances added", "ld.addAnInstance": "Add an instance",