From 385cfb35d759d5f69e787f41ac315e1aba75ce87 Mon Sep 17 00:00:00 2001 From: Dananji Withana Date: Fri, 10 Nov 2023 10:55:06 -0500 Subject: [PATCH] Fix structure display for section items with Canvas references (#284) * Fix structure display for section items with Canvas references * Remove debug console.log and fix sample manifest --- public/manifests/dev/lunchroom_manners.json | 4 ++ public/manifests/prod/lunchroom_manners.json | 4 ++ .../MediaPlayer/VideoJS/VideoJSPlayer.js | 11 ----- .../StructuredNavigation/NavUtils/ListItem.js | 2 +- .../StructuredNavigation.js | 7 ++- .../StructuredNavigation.test.js | 43 +++++++++++++++++-- src/services/iiif-parser.js | 4 +- src/services/iiif-parser.test.js | 18 ++++++++ src/test_data/lunchroom-manners.js | 6 ++- 9 files changed, 80 insertions(+), 19 deletions(-) diff --git a/public/manifests/dev/lunchroom_manners.json b/public/manifests/dev/lunchroom_manners.json index 7a6a29cb..1efd2180 100644 --- a/public/manifests/dev/lunchroom_manners.json +++ b/public/manifests/dev/lunchroom_manners.json @@ -435,6 +435,10 @@ ] }, "items": [ + { + "id": "http://localhost:3003/dev/lunchroom_manners/canvas/1#t=0,572.034", + "type": "Canvas" + }, { "id": "http://localhost:3003/dev/lunchroom_manners/range/1-1", "type": "Range", diff --git a/public/manifests/prod/lunchroom_manners.json b/public/manifests/prod/lunchroom_manners.json index 42046f22..9269a3b7 100644 --- a/public/manifests/prod/lunchroom_manners.json +++ b/public/manifests/prod/lunchroom_manners.json @@ -307,6 +307,10 @@ ] }, "items": [ + { + "id": "https://iiif-react-media-player.netlify.app/prod/lunchroom_manners/canvas/1#t=0,572.034", + "type": "Canvas" + }, { "id": "https://iiif-react-media-player.netlify.app/prod/lunchroom_manners/range/1-1", "type": "Range", diff --git a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js index b5351863..ea5c78a2 100644 --- a/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js +++ b/src/components/MediaPlayer/VideoJS/VideoJSPlayer.js @@ -335,17 +335,6 @@ function VideoJSPlayer({ ]); } } - } else if (startTime === null && canvasSegmentsRef.current.length > 0 && isReady) { - // When canvas gets loaded into the player, set the currentNavItem and startTime - // if there's a media fragment starting from time 0.0. - // This then triggers the creation of a fragment highlight in the player's timerail - const firsItem = canvasSegmentsRef.current[0]; - const timeFragment = getMediaFragment(firsItem.id, canvasDuration); - if (timeFragment && timeFragment.start === 0) { - manifestDispatch({ - item: firsItem, type: 'switchItem' - }); - } } }, [currentNavItem, isReady, canvasSegments]); diff --git a/src/components/StructuredNavigation/NavUtils/ListItem.js b/src/components/StructuredNavigation/NavUtils/ListItem.js index 2b77945e..420ab7db 100644 --- a/src/components/StructuredNavigation/NavUtils/ListItem.js +++ b/src/components/StructuredNavigation/NavUtils/ListItem.js @@ -64,7 +64,7 @@ const ListItem = ({ }); React.useEffect(() => { - if (liRef.current) { + if (liRef.current && !isCanvas) { if (currentNavItem && currentNavItem.id == itemIdRef.current) { liRef.current.className += ' active'; diff --git a/src/components/StructuredNavigation/StructuredNavigation.js b/src/components/StructuredNavigation/StructuredNavigation.js index 228ff204..43298097 100644 --- a/src/components/StructuredNavigation/StructuredNavigation.js +++ b/src/components/StructuredNavigation/StructuredNavigation.js @@ -67,7 +67,12 @@ const StructuredNavigation = () => { if (isClicked) { const clickedItem = canvasSegments.filter(c => c.id === clickedUrl); if (clickedItem?.length > 0) { - manifestDispatch({ item: clickedItem[0], type: 'switchItem' }); + // Only update the current nav item for timespans + // Eliminate Canvas level items unless the structure is empty + const { isCanvas, items } = clickedItem[0]; + if (!isCanvas || (items.length == 0 && isCanvas)) { + manifestDispatch({ item: clickedItem[0], type: 'switchItem' }); + } } const currentCanvasIndex = getCanvasIndex(manifest, getCanvasId(clickedUrl)); const timeFragment = getMediaFragment(clickedUrl, canvasDuration); diff --git a/src/components/StructuredNavigation/StructuredNavigation.test.js b/src/components/StructuredNavigation/StructuredNavigation.test.js index d7f726b7..22d5681e 100644 --- a/src/components/StructuredNavigation/StructuredNavigation.test.js +++ b/src/components/StructuredNavigation/StructuredNavigation.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import StructuredNavigation from './StructuredNavigation'; +import manifestWoCanvasRefs from '@TestData/transcript-annotation'; import manifest from '@TestData/lunchroom-manners'; import { withManifestProvider, @@ -11,7 +12,7 @@ import playlist from '@TestData/playlist'; describe('StructuredNavigation component', () => { describe('with manifest', () => { - describe('with structures', () => { + describe('with structures including Canvas references for sections', () => { beforeEach(() => { // An example of how we could pass props into // the tested(in this case: StructuredNavigation) component directly @@ -41,7 +42,7 @@ describe('StructuredNavigation component', () => { expect(screen.getAllByTestId('list').length).toBeGreaterThan(0); }); - test('first item is a section title', () => { + test('first item is a section title as a button', () => { const firstItem = screen.getAllByTestId('list-item')[0]; expect(firstItem.children[0]).toHaveTextContent( 'Lunchroom Manners' @@ -49,9 +50,43 @@ describe('StructuredNavigation component', () => { expect(firstItem.children[0]).toHaveClass( 'ramp--structured-nav__section' ); - expect(firstItem.children[0].children[0]).toHaveClass( - 'ramp--structured-nav__section-title' + expect(firstItem.children[0].children[0].tagName).toBe('BUTTON'); + }); + }); + + describe('with structures without Canvas references for sections', () => { + beforeEach(() => { + const NavWithPlayer = withPlayerProvider(StructuredNavigation, { + initialState: {}, + }); + const NavWithManifest = withManifestProvider(NavWithPlayer, { + initialState: { + manifest: manifestWoCanvasRefs, + canvasIndex: 0, + canvasSegments: [], + playlist: { isPlaylist: false } + }, + }); + render(); + }); + + test('renders successfully', () => { + expect(screen.getByTestId('structured-nav')); + }); + + test('returns a List of items when structures are present in the manifest', () => { + expect(screen.getAllByTestId('list').length).toBeGreaterThan(0); + }); + + test('first item is a section title as a span', () => { + const firstItem = screen.getAllByTestId('list-item')[0]; + expect(firstItem.children[0]).toHaveTextContent( + 'CD1 - Mahler, Symphony No.3' + ); + expect(firstItem.children[0]).toHaveClass( + 'ramp--structured-nav__section' ); + expect(firstItem.children[0].children[0].tagName).toBe('SPAN'); }); }); diff --git a/src/services/iiif-parser.js b/src/services/iiif-parser.js index 244e4dc0..46444746 100644 --- a/src/services/iiif-parser.js +++ b/src/services/iiif-parser.js @@ -480,7 +480,9 @@ export function getStructureRanges(manifest) { label: label, isTitle: canvases.length === 0 ? true : false, rangeId: range.id, - id: canvases.length > 0 ? canvases[0] : undefined, + id: canvases.length > 0 + ? isCanvas ? `${canvases[0].split(',')[0]},` : canvases[0] + : undefined, isEmpty: isEmpty, isCanvas: isCanvas, itemIndex: isCanvas ? cIndex : undefined, diff --git a/src/services/iiif-parser.test.js b/src/services/iiif-parser.test.js index a8547c20..a4315b57 100644 --- a/src/services/iiif-parser.test.js +++ b/src/services/iiif-parser.test.js @@ -415,6 +415,24 @@ describe('iiif-parser', () => { expect(firstTimespan).toEqual(firstStructCanvas); }); + it('returns mediafragment with only start time for sections with structure', () => { + const { structures, timespans } = iiifParser.getStructureRanges(lunchroomManifest); + expect(structures).toHaveLength(1); + expect(timespans).toHaveLength(12); + + const firstStructCanvas = structures[0]; + expect(firstStructCanvas.label).toEqual('Lunchroom Manners'); + expect(firstStructCanvas.items).toHaveLength(3); + expect(firstStructCanvas.isCanvas).toBeTruthy(); + expect(firstStructCanvas.isEmpty).toBeFalsy(); + expect(firstStructCanvas.isTitle).toBeFalsy(); + expect(firstStructCanvas.rangeId).toEqual('https://example.com/manifest/lunchroom_manners/range/1'); + expect(firstStructCanvas.id).toEqual('https://example.com/manifest/lunchroom_manners/canvas/1#t=0,'); + expect(firstStructCanvas.isClickable).toBeTruthy(); + expect(firstStructCanvas.duration).toEqual('11:00'); + + }); + it('returns [] when structure is not present', () => { const { structures, timespans } = iiifParser.getStructureRanges(manifestWoStructure); expect(structures).toEqual([]); diff --git a/src/test_data/lunchroom-manners.js b/src/test_data/lunchroom-manners.js index 2965c14a..b0f9c84c 100644 --- a/src/test_data/lunchroom-manners.js +++ b/src/test_data/lunchroom-manners.js @@ -262,6 +262,10 @@ export default { type: 'Range', label: { en: ['Lunchroom Manners'] }, items: [ + { + id: 'https://example.com/manifest/lunchroom_manners/canvas/1#t=0,660', + type: 'Canvas', + }, { id: 'https://example.com/manifest/lunchroom_manners/range/1-1', type: 'Range', @@ -273,7 +277,7 @@ export default { label: { en: ['Using Soap'] }, items: [ { - id: 'https://example.com/manifest/lunchroom_manners/canvas/1#t=157,160', + id: 'https://example.com/manifest/lunchroom_manners/canvas/1#t=0,160', type: 'Canvas', }, ],