Used to create a custom extension from scratch.
The name should be unique. The type can be "source", "loader", "validator", "transform", "resource", "view", or "store". The createFilePondExtensionSet function uses this type to auto sort the extensions set.
Props are the default options available for configuring this extension, these options are passed to didSetProps when updated and are available on props.
And then there’s the extension factory which receives the extension state and methods to interact with the FilePond instance.
const MyExtension = createExtension({
name: 'MyResource',
type: 'resource',
props: {
// default options
},
factory: (state, pond) => {
// the extension state
const { props, didSetProps } = state;
// the internal api
const {
on,
insertEntries,
removeEntries,
updateEntry,
replaceEntry,
pushTask,
abortTasks,
setEntryExtensionStatus,
getEntryExtensionStatus,
} = pond;
// called when props are updated
didSetProps((props) => {
//
});
// a task that needs to run on an entry, these are queued, so only run if other tasks finish successfully
async function taskCustomJob(entry) {
//
}
// called when an entry is updated
function handleUpdateEntry(entry) {
//
}
// called when the entry list is updated
function handleUpdateEntries(entries) {
//
}
// when the entry list is updated we call handleUpdateEntries
const unsubUpdateEntryList = on('updateEntries', debounce(handleUpdateEntries));
// when an entry is updated we call handleUpdateEntry
const unsubUpdateEntry = on('updateEntry', handleUpdateEntry);
return {
destroy: () => {
unsubUpdateEntry();
unsubUpdateEntryList();
},
};
},
});
Creating a PDF Preview extension
This extension will use PDF.js to load a PDF and create a poster image of its first page. The poster image can then be displayed by the EntryListView extension.
import { defineFilePond } from 'filepond';
import { locale } from 'filepond/locales/en-gb.js';
import { createExtension, ConsoleView } from 'filepond/extensions/index.js';
import { createFilePondEntryList, appendEntryImageView } from 'filepond/templates/index.js';
// So we can see the image preview
const template = createFilePondEntryList();
appendEntryImageView(template);
// the PDFPageResource extension, it'll read a PDF and generate a PNG of the first page
const PDFPageResource = createExtension({
name: 'PDFPageResource',
type: 'resource',
props: {
page: 1,
pixelRatio: 1,
},
factory: (
{ props, extensionName },
{
on,
updateEntry,
pushTask,
getEntryExtensionState,
setEntryExtensionState,
getEntryExtensionStatus,
setEntryExtensionStatus,
}
) => {
// very basic pdf detection
function isPDF(file) {
return file.type === 'application/pdf';
}
// this task will generate the PDF
async function taskRenderPDFPage(entry) {
const { page: pageNr, pixelRatio: scale } = props;
// let's start the loading indicator
setEntryExtensionStatus(entry, {
type: 'system',
code: 'RESOURCE_BUSY',
progress: Infinity,
});
// try to create a screenshot of the first page
try {
// dynamically load pdfjs and set its worker url
const cdn = 'https://cdn.jsdelivr.net/npm/pdfjs-dist';
const { getDocument, GlobalWorkerOptions } = await import(/* @vite-ignore */ cdn);
GlobalWorkerOptions.workerSrc = cdn + '/build/pdf.worker.min.mjs';
// let's convert the pdf to a png
const objectURL = URL.createObjectURL(entry.file);
const pdf = await getDocument(objectURL).promise;
// we've got the document, release the url
URL.revokeObjectURL(objectURL);
// get first page
const page = await pdf.getPage(pageNr);
// get a scaled viewport for the pdf
const viewport = page.getViewport({ scale });
// create the target canvas to draw on
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
// ask pdfjs to draw to the canvas
await page.render({
canvasContext: canvas.getContext('2d'),
transform: null,
viewport: viewport,
}).promise;
// we turn the canvas into a blob
const blob = await new Promise((resolve) => canvas.toBlob(resolve));
// update state of the entry with the rendered page
updateEntry(entry, {
extension: {
[extensionName]: {
value: blob,
},
},
});
} catch (error) {
setEntryExtensionStatus(entry, {
type: 'error',
code: 'RESOURCE_ERROR',
values: { error },
});
// so scheduler aborts rest of tasks
throw error;
}
// done!
setEntryExtensionStatus(entry, {
type: 'success',
code: 'RESOURCE_COMPLETE',
});
}
// this will be called everytime the entry is updated
function handleUpdateEntryData(entry) {
// get extension entry props to help determine what next step to take
const status = getEntryExtensionStatus(entry);
const { value } = getEntryExtensionState(entry);
// is in error state
const hasFailed = status?.type === 'error';
// already has generated preview
const hasPoster = value instanceof File;
// failed to generate preview, not ready yet, or already generated the preview
if (hasFailed || !(entry.file instanceof File) || !isPDF(entry.file) || hasPoster) {
return;
}
// let's queue our preview generation task
pushTask(entry.id, taskRenderPDFPage);
}
const unsubUpdateEntryData = on('updateEntryData', handleUpdateEntryData);
return {
destroy: () => {
unsubUpdateEntryData();
},
};
},
});
// Create FilePond
const [element] = defineFilePond({
locale,
// set custom extensions
extensions: [PDFPageResource, ConsoleView],
// overwrite list view props
EntryListView: {
template,
},
});
// we need to pass the PDF page blob to the entry list view extension
element.on('updateEntry', (entry) => {
const { PDFPageResource, EntryListView } = entry.extension;
const pdf = PDFPageResource?.value;
const poster = EntryListView?.poster;
// wait till we have a pdf or the pdf has changed, else we end up in infinite loop
if (!pdf || pdf === poster) {
return;
}
// update the entry poster
element.updateEntry(entry.id, {
extension: {
EntryListView: {
poster: pdf,
},
},
});
});