From 423db5afda6f15a06c1ccf89cbf0cf62e270f822 Mon Sep 17 00:00:00 2001 From: gestchild Date: Mon, 8 Mar 2021 16:11:48 +0000 Subject: [PATCH 1/3] add lodash clonedeep --- common/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/common/package.json b/common/package.json index 73b952dbb3..4939c1250a 100644 --- a/common/package.json +++ b/common/package.json @@ -41,6 +41,7 @@ "jest": "^24.9.0", "koa-compose": "^4.1.0", "lazysizes": "^5.2.1", + "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.flattendeep": "^4.4.0", "lodash.groupby": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 2b051e8d8d..4f9314de5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15279,6 +15279,11 @@ lodash-es@^4.17.15: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" From 02f678ce52d78a5beb448c7143528b30ad2a37bc Mon Sep 17 00:00:00 2001 From: gestchild Date: Mon, 8 Mar 2021 16:12:35 +0000 Subject: [PATCH 2/3] write function to group similar structures --- common/test/utils/iiif.test.ts | 85 ++++++++++++++++++++++++++++++++++ common/utils/iiif.ts | 46 +++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 common/test/utils/iiif.test.ts diff --git a/common/test/utils/iiif.test.ts b/common/test/utils/iiif.test.ts new file mode 100644 index 0000000000..6f9476de7d --- /dev/null +++ b/common/test/utils/iiif.test.ts @@ -0,0 +1,85 @@ +import { getCanvases, groupStructures } from '../../utils/iiif'; +import manifest from '../../../common/__mocks__/iiif-manifest'; + +const canvases = getCanvases(manifest); +const structures = [ + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-0', + '@type': 'sc:Range', + label: 'First', + canvases: [ + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c0', + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c1', + ], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-1', + '@type': 'sc:Range', + label: 'First', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c2'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-2', + '@type': 'sc:Range', + label: 'First', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c5'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-3', + '@type': 'sc:Range', + label: 'Second', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c6'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-4', + '@type': 'sc:Range', + label: 'Third', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c8'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-5', + '@type': 'sc:Range', + label: 'Third', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c9'], + }, +]; + +const correctResult = [ + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-0', + '@type': 'sc:Range', + label: 'First', + canvases: [ + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c0', + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c1', + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c2', + ], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-2', + '@type': 'sc:Range', + label: 'First', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c5'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-3', + '@type': 'sc:Range', + label: 'Second', + canvases: ['https://wellcomelibrary.org/iiif/b21538906/canvas/c6'], + }, + { + '@id': 'https://wellcomelibrary.org/iiif/b21538906/range/r-4', + '@type': 'sc:Range', + label: 'Third', + canvases: [ + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c8', + 'https://wellcomelibrary.org/iiif/b21538906/canvas/c9', + ], + }, +]; +describe('Group repetitive iiif structures', () => { + it('groups iiifStructures with consecutive canvases and the same label', () => { + const groupedStructures = groupStructures(canvases, structures); + expect(groupedStructures).toEqual(correctResult); + }); +}); diff --git a/common/utils/iiif.ts b/common/utils/iiif.ts index d97a49dadf..b1366b59b4 100644 --- a/common/utils/iiif.ts +++ b/common/utils/iiif.ts @@ -9,6 +9,7 @@ import { AuthService, IIIFAnnotationResource, } from '../model/iiif'; +import cloneDeep from 'lodash.clonedeep'; export function getServiceId(canvas?: IIIFCanvas): string | undefined { const serviceSrc = canvas?.images[0]?.resource?.service; @@ -64,7 +65,10 @@ export function getUiExtensions( } } -export function isUiEnabled(uiExtensions: Service | undefined, uiName: string) { +export function isUiEnabled( + uiExtensions: Service | undefined, + uiName: string +): boolean { const disableUI = uiExtensions && uiExtensions.disableUI; if (disableUI) { return !( @@ -130,6 +134,46 @@ export function getStructures(iiifManifest: IIIFManifest): IIIFStructure[] { return iiifManifest?.structures || []; } +// When lots of the works were digitised, structures with multiple pages such as table of contents +// were created individually, rather than as a single structure with a range of pages. +// This means we will often display repetitive links to the essentially the same thing. +// Until we can improve the data at source, this function groups structures that have the same label attached to consecutive pages into a single structure. +export function groupStructures( + canvases: IIIFCanvas[], + structures: IIIFStructure[] +): IIIFStructure[] { + const clonedStructures = cloneDeep(structures); + return clonedStructures.reduce( + (acc, structure) => { + const [lastCanvasInRange] = structure.canvases.slice(-1); + const [firstCanvasInRange] = structure.canvases; + const firstCanvasIndex = canvases.findIndex( + canvas => canvas['@id'] === firstCanvasInRange + ); + if ( + acc.previousLabel === structure.label && + firstCanvasIndex === acc.previousLastCanvasIndex + 1 + ) { + acc.groupedArray[acc.groupedArray.length - 1].canvases.push( + lastCanvasInRange + ); + } else { + acc.groupedArray.push(structure); + } + acc.previousLabel = structure.label; + acc.previousLastCanvasIndex = canvases.findIndex( + canvas => canvas['@id'] === lastCanvasInRange + ); + return acc; + }, + { + previousLastCanvasIndex: null, + previousLabel: null, + groupedArray: [], + } + ).groupedArray; +} + export function getVideo( iiifManifest: IIIFManifest ): IIIFMediaElement | undefined { From 45ec2ba8a5e3a14c76feca3f8a3682713d75fd65 Mon Sep 17 00:00:00 2001 From: gestchild Date: Mon, 15 Mar 2021 12:00:54 +0000 Subject: [PATCH 3/3] use groupStructures function in StructuresViewer --- .../ViewerStructuresPrototype.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/views/components/ViewerStructuresPrototype/ViewerStructuresPrototype.tsx b/common/views/components/ViewerStructuresPrototype/ViewerStructuresPrototype.tsx index 8e19b0c377..814b75f172 100644 --- a/common/views/components/ViewerStructuresPrototype/ViewerStructuresPrototype.tsx +++ b/common/views/components/ViewerStructuresPrototype/ViewerStructuresPrototype.tsx @@ -1,5 +1,9 @@ import { IIIFManifest } from '@weco/common/model/iiif'; -import { getStructures, getCanvases } from '@weco/common/utils/iiif'; +import { + getStructures, + groupStructures, + getCanvases, +} from '@weco/common/utils/iiif'; import { FunctionComponent, RefObject } from 'react'; import { FixedSizeList } from 'react-window'; @@ -15,10 +19,11 @@ const ViewerStructuresPrototype: FunctionComponent = ({ }: Props) => { const structures = manifest ? getStructures(manifest) : []; const canvases = manifest ? getCanvases(manifest) : []; + const groupedStructures = groupStructures(canvases, structures); - return structures.length > 0 ? ( + return groupedStructures.length > 0 ? (
    - {structures.map((structure, i) => { + {groupedStructures.map((structure, i) => { const firstCanvasInRange = structure.canvases[0]; const canvasIndex = canvases.findIndex( canvas => canvas['@id'] === firstCanvasInRange