Skip to content

Commit

Permalink
Merge pull request #149 from samvera-labs/4230-custom-display-poc
Browse files Browse the repository at this point in the history
Support custom canvas displays
  • Loading branch information
adamjarling authored Jan 3, 2024
2 parents 259eb96 + f86ad53 commit 448b563
Show file tree
Hide file tree
Showing 21 changed files with 2,480 additions and 23 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ coverage/
html/

# OS
.DS_Store
.DS_Store

# VSCode
.vscode/
33 changes: 33 additions & 0 deletions docs/components/CustomDisplays/PDFViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Example of a custom component which accepts a Canvas id from
* a Manifest. It then renders expected PDF content by wrapping
* in a simple iFrame (uses browser's default PDF renderer).
*
* One could make this as complex as they like, for flexible implementations.
*/

import { LabeledIIIFExternalWebResource } from "src/types/presentation-3";

const PDFViewer = ({
id,
annotationBody,
...restProps
}: {
id: string;
annotationBody: LabeledIIIFExternalWebResource;
restProps: {
[key: string]: unknown;
};
}) => {
return (
<>
<p>CustomProps:</p>
<pre style={{ paddingBottom: "1rem" }}>{JSON.stringify(restProps)}</pre>
<div style={{ background: "hotpink", padding: "2rem" }}>
<iframe id={id} src={annotationBody.id} width="100%" height="600" />
</div>
</>
);
};

export default PDFViewer;
8 changes: 7 additions & 1 deletion docs/components/DynamicImports/Viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { ViewerConfigOptions } from "src/context/viewer-context";
import {
type CustomDisplay,
ViewerConfigOptions,
} from "src/context/viewer-context";
import dynamic from "next/dynamic";
import { isDark } from "docs/lib/theme";
import { useRouter } from "next/router";
Expand All @@ -14,9 +17,11 @@ const Viewer = dynamic(() => import("src/components/Viewer"), {
const CloverViewer = ({
iiifContent = defaultIiifContent,
options,
customDisplays,
}: {
iiifContent: string;
options?: ViewerConfigOptions;
customDisplays?: Array<CustomDisplay>;
}) => {
const router = useRouter();
const iiifResource = router.query["iiif-content"]
Expand All @@ -30,6 +35,7 @@ const CloverViewer = ({
iiifContent={iiifResource}
options={{ ...options, background }}
key={iiifContent}
{...(customDisplays && { customDisplays })}
/>
);
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@iiif/presentation-3": "^1.1.3",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/node": "20.7.2",
"@types/openseadragon": "^3.0.7",
"@types/react": "^18.2.23",
Expand Down
92 changes: 92 additions & 0 deletions pages/docs/viewer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const MyCustomViewer = () => {
| ------------------------------- | --------------------------------------------------------------------------- | -------- | ---------------------------------------- |
| `iiifContent` | `string` | Yes | |
| `canvasIdCallback` | `function` | No | |
| `customDisplays` | [See Custom Displays](#custom-displays) | No | |
| `customTheme` | `object` | No | |
| `options` | `object` | No | |
| `options.background` | `string` [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/background) | No | `transparent` |
Expand Down Expand Up @@ -174,6 +175,19 @@ The information panel is a collapsible panel that displays information about the
| `options.informationPanel.renderSupplementing` | `boolean` | No | true |
| `options.informationPanel.renderToggle` | `boolean` | No | true |

### Custom Displays

Clients may wish to use their own display components (for example a PDF Viewer, or an audio player, etc). To configure custom displays, use the `customDisplays` prop, which is an array of objects defining `display` and `target` properties. [See an example implementation](#custom-canvas-displays)

`display.component` is a custom React component, and `display.componentProps` are pass-through props which `Viewer` will attach to your Custom Display component. The `target` object provides two methods of matching a Canvas to a Custom Display: `target.canvasId` which is a manifest's canvas `id`. Or by `target.paintingFormat` (ie. `application/pdf`) which is the `body.type` in a canvas's `Annotation` of type "painting".

| Prop | Type | Required | Default |
| --------------------------------------- | ------------ | -------- | ------- |
| `customDisplays.display.component` | `React.Node` | No | |
| `customDisplays.display.componentProps` | `object` | No | |
| `customDisplays.target.canvasId` | `string[]` | No | |
| `customDisplays.target.paintingFormat` | `string[]` | No | |

### Deprecated Options

| Prop | In Favor Of | Deprecated |
Expand Down Expand Up @@ -333,3 +347,81 @@ return (
/>
);
```

---

### Custom canvas displays

Clients may wish to use their own display components instead of Clover Viewer's default displays ([OpenSeadragon](https://openseadragon.github.io/) for images and [HTML Video Player](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) for audio/video). The `Viewer` component allows a client to target individual `canvas` items in a IIIF Manifest by either direct reference to a canvas `id` or `format` (ie. `video/ogg`). See the Type Definition below for `CustomDisplay`, and an example implementation.

```tsx {16, 29-46}
import AnotherCustomDisplay from './AnotherCustomDisplay';

type CustomDisplay = {
display: {
component: React.ElementType;
componentProps: { // Any custom props you want to pass to your component
[key: string]: any;
}
};
target: {
canvasId: string[];
paintingFormat: string[]; // "application/pdf" or "application/epub+zip"
};
};

function MyCustomDisplay({ id, annotationBody, ...restProps }: CustomDisplay) {
return (
<div>
<h1>My Custom Display</h1>
<p>Canvas ID: {id}</p>
<p>Annotation Body: {JSON.stringify(annotationBody)}</p>
<p>Custom props: {JSON.stringify(restProps)}</p>
...your display here
</div>
);
}

<Viewer
iiifContent={iiifContent}
customDisplays=[{
display: {
component: MyCustomDisplay,
componentProps: {
foo: "bar"
}
},
target: {
canvasId: ["https://uri-for-a-canvas-id/access/0", "https://uri-for-a-canvas-id/access/1"],
}
}, {
display: {
component: AnotherCustomDisplay
},
target: {
paintingFormat: ["application/pdf", "image/gif"],
},
}]
/>
```

The `Viewer` component will pass the following props to your custom display component:

- `id`: The canvas `id` for the resource being rendered. This may be helpful if you wanted to use the canvas `id` to fetch additional data from your application's API.
- `annotationBody`: The `body` value for a canvas `Annotation` item with `motivation` "painting".

```json {5-10}
{
"id": "https://uri-for-a-canvas-id/access/0",
"type": "Annotation",
"motivation": "painting",
"body": {
"format": "application/pdf",
"height": 1686,
"id": "http://localhost:3000/media/pdf/file-sample_150kB.pdf",
"width": 1192
}
}
```

[See a complete recipe for a PDF Viewer](https://github.com/samvera-labs/clover-iiif/blob/4230-custom-display-poc/docs/components/CustomDisplays/PDFViewer.tsx) using custom canvas displays.
2 changes: 0 additions & 2 deletions pages/docs/viewer/demo.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Viewer from "docs/components/DynamicImports/Viewer";
import CustomManifest from "docs/components/CustomManifest/CustomManifest";
import { Bleed } from "nextra-theme-docs";
import { Tabs, Tab } from "nextra/components";
import CallToAction from "docs/components/CallToAction";

## Viewer
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 448b563

Please sign in to comment.