Skip to content

Commit f56cad0

Browse files
committed
Add document manager example
1 parent 631a18d commit f56cad0

3 files changed

Lines changed: 282 additions & 0 deletions

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
'use client'
2+
import {
3+
PDFViewer,
4+
PDFViewerRef,
5+
DocumentManagerPlugin,
6+
} from '@embedpdf/react-pdf-viewer'
7+
import { useRef, useState, useEffect } from 'react'
8+
import { FileUp, Globe, Layers } from 'lucide-react'
9+
10+
export default function DocumentLoadingExample() {
11+
const viewerRef = useRef<PDFViewerRef>(null)
12+
const [activeDocId, setActiveDocId] = useState<string | null>(null)
13+
const [docList, setDocList] = useState<{ id: string; name: string }[]>([])
14+
15+
// Helper to access the plugin
16+
const getDocManager = async () => {
17+
const registry = await viewerRef.current?.registry
18+
return registry
19+
?.getPlugin<DocumentManagerPlugin>('document-manager')
20+
?.provides()
21+
}
22+
23+
// Sync state with viewer events
24+
useEffect(() => {
25+
const sync = async () => {
26+
const docManager = await getDocManager()
27+
if (!docManager) return
28+
29+
const updateList = () => {
30+
const docs = docManager.getOpenDocuments()
31+
setDocList(docs.map((d) => ({ id: d.id, name: d.name || 'Untitled' })))
32+
setActiveDocId(docManager.getActiveDocumentId())
33+
}
34+
35+
// Listen for any document changes
36+
const unsub1 = docManager.onDocumentOpened(updateList)
37+
const unsub2 = docManager.onDocumentClosed(updateList)
38+
const unsub3 = docManager.onActiveDocumentChanged((e) =>
39+
setActiveDocId(e.currentDocumentId),
40+
)
41+
42+
// Initial fetch
43+
updateList()
44+
45+
return () => {
46+
unsub1()
47+
unsub2()
48+
unsub3()
49+
}
50+
}
51+
sync()
52+
}, [])
53+
54+
const handleUrlLoad = async () => {
55+
const docManager = await getDocManager()
56+
docManager?.openDocumentUrl({
57+
url: 'https://snippet.embedpdf.com/ebook.pdf',
58+
documentId: 'ebook-demo', // Optional: fixed ID
59+
})
60+
}
61+
62+
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
63+
const file = e.target.files?.[0]
64+
if (!file) return
65+
66+
const buffer = await file.arrayBuffer()
67+
const docManager = await getDocManager()
68+
69+
docManager?.openDocumentBuffer({
70+
buffer,
71+
name: file.name,
72+
autoActivate: true,
73+
})
74+
75+
// Reset input
76+
e.target.value = ''
77+
}
78+
79+
const handleSwitch = async (id: string) => {
80+
const docManager = await getDocManager()
81+
docManager?.setActiveDocument(id)
82+
}
83+
84+
return (
85+
<div className="flex flex-col gap-4">
86+
{/* External Control Panel */}
87+
<div className="flex flex-wrap items-center justify-between gap-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800">
88+
{/* Load Actions */}
89+
<div className="flex gap-2">
90+
<button
91+
onClick={handleUrlLoad}
92+
className="flex items-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium shadow-sm hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600"
93+
>
94+
<Globe size={16} /> Load URL
95+
</button>
96+
97+
<label className="flex cursor-pointer items-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium shadow-sm hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600">
98+
<FileUp size={16} /> Upload Local
99+
<input
100+
type="file"
101+
className="hidden"
102+
accept=".pdf"
103+
onChange={handleFileUpload}
104+
/>
105+
</label>
106+
</div>
107+
108+
{/* Document Switcher */}
109+
<div className="flex items-center gap-2">
110+
<Layers size={16} className="text-gray-500" />
111+
<select
112+
value={activeDocId || ''}
113+
onChange={(e) => handleSwitch(e.target.value)}
114+
className="rounded-md border-gray-300 bg-white py-1.5 text-sm dark:bg-gray-700"
115+
disabled={docList.length === 0}
116+
>
117+
<option value="" disabled>
118+
Select Document...
119+
</option>
120+
{docList.map((doc) => (
121+
<option key={doc.id} value={doc.id}>
122+
{doc.name}
123+
</option>
124+
))}
125+
</select>
126+
</div>
127+
</div>
128+
129+
{/* Viewer */}
130+
<div className="h-[500px] w-full overflow-hidden rounded-xl border border-gray-300 shadow-lg dark:border-gray-600">
131+
<PDFViewer
132+
ref={viewerRef}
133+
config={{
134+
theme: { preference: 'light' },
135+
// Enable built-in tab bar
136+
tabBar: 'always',
137+
documentManager: {
138+
maxDocuments: 5,
139+
},
140+
}}
141+
style={{ width: '100%', height: '100%' }}
142+
/>
143+
</div>
144+
</div>
145+
)
146+
}

website/src/content/docs/snippet/plugins/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export default {
2+
'plugin-document-manager': 'Loading & Documents',
23
'plugin-i18n': 'Internationalization',
34
'plugin-scroll': 'Scrolling & Navigation',
45
'plugin-export': 'Export',
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
title: Loading & Documents
3+
description: Open PDFs from URLs, local files (buffers), and manage multi-document tabs programmatically.
4+
---
5+
6+
import DocumentLoadingExample from '../code-examples/document-loading-example';
7+
import { ExampleWrapper } from '@/components/example-wrapper';
8+
9+
# Loading & Managing Documents
10+
11+
The Snippet includes a robust `document-manager` plugin that handles file loading, error states, and tab management. While the viewer provides a built-in "Open File" button, you often need to trigger these actions from your own application logic—for example, loading a file when a user clicks a link in your dashboard.
12+
13+
## Configuration
14+
15+
You can configure initial documents and limits via the `documentManager` property.
16+
17+
```javascript
18+
const viewer = EmbedPDF.init({
19+
// ...
20+
documentManager: {
21+
// Load these files on startup
22+
initialDocuments: [
23+
{
24+
url: 'https://snippet.embedpdf.com/ebook.pdf',
25+
// By default, autoActivate is true.
26+
// This document will open and become active.
27+
autoActivate: true,
28+
// OPTIONAL: Set a custom ID so you can easily reference
29+
// this document later (e.g. to scroll or close it).
30+
documentId: 'ebook-embedpdf',
31+
},
32+
{
33+
url: 'https://snippet.embedpdf.com/ebook.pdf',
34+
// If we don't set this to false, this document would
35+
// steal focus immediately after loading!
36+
autoActivate: false
37+
}
38+
],
39+
// Limit total open tabs (older ones may be closed)
40+
maxDocuments: 10
41+
},
42+
// Show the built-in tab bar if you want tabs inside the viewer
43+
tabBar: 'multiple' // 'always', 'multiple' (default), or 'never'
44+
});
45+
```
46+
47+
## Programmatic Loading
48+
49+
You can control document loading using the `document-manager` plugin API.
50+
51+
<CodeExample
52+
codePaths={[
53+
"content/docs/snippet/code-examples/document-loading-example.tsx"
54+
]}
55+
>
56+
<ExampleWrapper>
57+
<DocumentLoadingExample />
58+
</ExampleWrapper>
59+
</CodeExample>
60+
61+
### Loading from URL
62+
63+
Use `openDocumentUrl` to load remote files.
64+
65+
```javascript
66+
const registry = await viewer.registry;
67+
const docManager = registry.getPlugin('document-manager').provides();
68+
69+
docManager.openDocumentUrl({
70+
url: 'https://snippet.embedpdf.com/ebook.pdf',
71+
documentId: 'invoice-123', // Optional: fixed ID for easy reference
72+
autoActivate: true // Switch tab to this document immediately
73+
});
74+
```
75+
76+
### Loading from Buffer (Local Files)
77+
78+
To load a file selected by the user (via `<input type="file">`) or fetched via a custom API request, use `openDocumentBuffer`. This accepts a raw `ArrayBuffer`.
79+
80+
```javascript
81+
// Example: Handling a file input change
82+
async function handleFileSelect(event) {
83+
const file = event.target.files[0];
84+
const buffer = await file.arrayBuffer();
85+
86+
const registry = await viewer.registry;
87+
const docManager = registry.getPlugin('document-manager').provides();
88+
89+
docManager.openDocumentBuffer({
90+
buffer: buffer,
91+
name: file.name, // Display name for the tab
92+
autoActivate: true
93+
});
94+
}
95+
```
96+
97+
### Native File Picker
98+
99+
You can also trigger the browser's native file picker dialog directly through the viewer API.
100+
101+
```javascript
102+
docManager.openFileDialog();
103+
```
104+
105+
## Managing Active Documents
106+
107+
You can switch tabs or close documents programmatically.
108+
109+
```javascript
110+
// Switch to a specific document
111+
docManager.setActiveDocument('invoice-123');
112+
113+
// Close a specific document
114+
docManager.closeDocument('invoice-123');
115+
116+
// Close all documents
117+
docManager.closeAllDocuments();
118+
```
119+
120+
## Events
121+
122+
You can listen for document lifecycle events to sync your external UI (like a sidebar list) with the viewer state.
123+
124+
| Event | Payload | Description |
125+
| --- | --- | --- |
126+
| **`onDocumentOpened`** | `DocumentState` | Fired when a document successfully loads. |
127+
| **`onDocumentClosed`** | `documentId` | Fired when a document tab is closed. |
128+
| **`onActiveDocumentChanged`** | `{ previous, current }` | Fired when the user switches tabs. |
129+
| **`onDocumentError`** | `{ documentId, error }` | Fired if a document fails to load (e.g. 404, corrupt PDF). |
130+
131+
```javascript
132+
docManager.onDocumentOpened((doc) => {
133+
console.log(`Opened: ${doc.name} (${doc.id})`);
134+
});
135+
```

0 commit comments

Comments
 (0)