> For the complete documentation index, see [llms.txt](https://docs.wellcomecollection.org/request-for-comments-rfcs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.wellcomecollection.org/request-for-comments-rfcs/086-item-viewer-refactor/10-phase-4-download-logic.md).

# Phase 4: Download Logic Hook

[← Back to Index](/request-for-comments-rfcs/086-item-viewer-refactor.md)

**Effort:** 2 hours (tests) + 2 hours (implementation) = **4 hours total**\
**Risk:** Medium (mitigated by tests)\
**Priority:** Medium\
**Previous:** [Phase 3: Canvas Data](/request-for-comments-rfcs/086-item-viewer-refactor/09-phase-3-canvas-data.md)\
**Next:** [Phase 5: Restriction Status](/request-for-comments-rfcs/086-item-viewer-refactor/11-phase-5-restriction-status.md)

## Goal

Extract download options logic from `ViewerTopBar.tsx` into a reusable custom hook. This removes \~65 lines of complex business logic from the component and makes it testable in isolation.

**Note:** The `restricted-images` merge already added download deduplication and improved ChoiceBody handling. This phase integrates those changes into the hook.

## Why Extract to a Hook (Not Context)?

**Current problem:** `ViewerTopBar.tsx` has 65+ lines of download option calculations:

* IIIF image downloads
* Canvas image downloads from services
* Canvas rendering downloads (PDFs)
* Manifest downloads
* Video/audio downloads
* ChoiceBody handling

**Why a hook instead of context?**

* Not for context: Only used in `ViewerTopBar` (one component)
* Perfect for hook: Complex logic (65 lines) worth extracting for testability
* Potentially reusable: Other components might need download options in the future

**Solution:** Move all this into `useDownloadOptions` hook:

* Easier to test (no component rendering needed)
* Reusable if other components need download options
* Clearer component code (`ViewerTopBar` focuses on presentation)
* Business logic separate from presentation

## Steps

### 2.1 Write Tests FIRST for `useDownloadOptions` Hook

**File:** `content/webapp/hooks/useDownloadOptions.test.ts`

```typescript
describe('useDownloadOptions', () => {
  it('should return empty array when no canvas or manifest', () => { ... });
  it('should include IIIF image download options', () => { ... });
  it('should include canvas image downloads from services', () => { ... });
  it('should include canvas rendering downloads (PDFs)', () => { ... });
  it('should include manifest-level downloads', () => { ... });
  it('should include video/audio downloads', () => { ... });
  it('should handle ChoiceBody items correctly', () => { ... });
  it('should deduplicate downloads with same id', () => { ... });  // NEW from restricted-images
  it('should memoise results and only recalculate when dependencies change', () => { ... });
});

describe('useImageServices', () => {
  it('should extract image services from canvas painting', () => { ... });
  it('should return empty array when no painting items', () => { ... });
  it('should handle undefined currentCanvas', () => { ... });
});
```

### 2.2 Create `useDownloadOptions` Hook

**File:** `content/webapp/hooks/useDownloadOptions.ts`

```typescript
export function useDownloadOptions(iiifImageLocation?: DigitalLocation) {
  const { work, currentCanvas, transformedManifest } = useItemViewerContext();
  const transformedIIIFImage = useTransformedIIIFImage(work);

  return useMemo(() => {
    // Extract image services
    const imageServices = /* ... */;
    
    // Calculate all download options
    const iiifImageDownloadOptions = /* ... */;
    const canvasImageDownloads = /* ... */;
    const canvasDownloadOptions = /* ... */;
    const manifestDownloadOptions = /* ... */;
    const videoAudioDownloadOptions = /* ... */;

    // Combine all options
    const allOptions = [
      ...iiifImageDownloadOptions,
      ...canvasImageDownloads,
      ...canvasDownloadOptions,
      ...manifestDownloadOptions,
      ...videoAudioDownloadOptions,
    ];

    // Deduplicate by download URL (id)
    // Added in restricted-images merge - prevents same file appearing multiple times
    // when it's available from multiple sources in the IIIF manifest
    return allOptions.filter(
      (option, index, self) => 
        self.findIndex(o => o.id === option.id) === index
    );
  }, [currentCanvas, transformedManifest, iiifImageLocation, /* ... */]);
}
```

**Note:** The `restricted-images` merge improved `getDownloadOptionsFromCanvasRenderingAndSupplementing` to properly handle ChoiceBody items:

```typescript
// In utils/iiif/v3/index.ts (already implemented)
export function getDownloadOptionsFromCanvasRenderingAndSupplementing(
  canvas: TransformedCanvas
): DownloadOption[] {
  return [...canvas.rendering, ...canvas.supplementing]
    .flatMap(item => (isChoiceBody(item) ? item.items : [item]))
    .filter((item): item is ContentResource => typeof item !== 'string')
    .map(convertToDownloadOption);
}
```

### 2.3 Update `ViewerTopBar` to Use Hook

**File:** `content/webapp/views/pages/works/work/IIIFViewer/ViewerTopBar.refactored.tsx`

```typescript
// ADD import:
import { useDownloadOptions } from '@weco/content/hooks/useDownloadOptions';

// DELETE lines 226-291 (all download calculation logic)

// REPLACE with:
const downloadOptions = useDownloadOptions(iiifImageLocation);
```

Result: \~65 lines removed from `ViewerTopBar`.

### 2.4 Run Tests

```bash
yarn test useDownloadOptions.test
yarn test ViewerTopBar.refactored.test
```

All should pass.

### 2.5 Manual Testing

* [ ] Download dropdown appears
* [ ] All download options present:
  * [ ] IIIF image downloads (various sizes)
  * [ ] Canvas image downloads
  * [ ] PDF downloads (if applicable)
  * [ ] Manifest downloads (if applicable)
  * [ ] Video/audio downloads (if applicable)
* [ ] Download links work correctly
* [ ] Options update when navigating between canvases

## Success Criteria

* [ ] Tests for `useDownloadOptions` pass
* [ ] `ViewerTopBar` \~65 lines shorter
* [ ] Download dropdown still works identically
* [ ] All download types still appear correctly

## Time Breakdown

* 2 hours: Write comprehensive tests for hook
* 1 hour: Extract logic to hook
* 1 hour: Update ViewerTopBar to use hook
* 30 mins: Manual testing

Total: \~4 hours

***

**Next:** [Phase 5: Restriction Status](/request-for-comments-rfcs/086-item-viewer-refactor/11-phase-5-restriction-status.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.wellcomecollection.org/request-for-comments-rfcs/086-item-viewer-refactor/10-phase-4-download-logic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
