netresearch / assetpicker
Embeddable Vue 3 asset & file picker with a pluggable storage-adapter layer — GitHub, Google Drive, EnterMediaDB, or your own.
Package info
github.com/netresearch/assetpicker
Language:JavaScript
pkg:composer/netresearch/assetpicker
Requires
- jenssegers/proxy: ^2.2
README
AssetPicker is a free asset / file picker for web application interfaces. A file abstraction layer lets adapters connect to any remote storage — GitHub, Google Drive, EnterMediaDB, cloud storages, or a custom backend — and it handles hierarchical (folder) as well as associative (search/category) stores. You embed it in your app, the user picks an asset in a modal, and your app receives the picked asset.
▶ Try the demo
- How it works
- Install & build
- Usage
- Configuration
- Adapters
- Write your own adapter
- PHP proxy
- Development
How it works
AssetPicker is a Vue 3 single-page app (app/, built with Vite). Your host app mounts it — typically into a modal overlay — with a config and an onFinish callback:
- The
config.storagesbecome the top-level entries; each storage names an adapter. - An adapter is a small factory implementing
list(item)/search(word), returning normalized items. The sidebar tree is the folder navigation; single-click a folder to open it. - The user clicks a file to select it and presses Select;
onFinish(result, cancelled)fires with the picked asset (or an array, forpick.limit ≠ 1).
There is no CDN script tag and no cross-window iframe messaging anymore — you mount the component directly, so the returned asset is a plain callback in your own page.
Migrated from a Vue 1.x code base. The public API is
createAssetPickerApp+ the adapter contract below.
Install & build
Requires a modern browser (Vue 3 / ES2015+). Tooling uses Bun (npm also works).
git clone https://github.com/netresearch/assetpicker.git cd assetpicker bun install bun run dev # Vite dev server (the demo) bun run build # production build into dist/ bun run test # Vitest unit tests
Usage
Mount the picker into an element (e.g. a modal you show on a button click) and handle the result:
import { createAssetPickerApp } from 'assetpicker'; const picker = createAssetPickerApp({ el: '#picker-mount', config: { pick: { limit: 1, types: ['file'], extensions: [] }, thumbnails: 'url', storages: { repo: { adapter: 'github', label: 'My repo', username: 'netresearch', repository: 'assetpicker' }, demo: { adapter: 'dummy', label: 'Demo storage' }, }, }, onFinish(result, cancelled) { picker.unmount(); // close your modal if (cancelled) return; // result is a single item (pick.limit === 1) or an item[] console.log('picked', result); }, });
app/src/main.js shows the full demo host (open on a button, render the picked asset). Each item is { id, storage, name, type: 'file' | 'dir', extension, thumbnail, links, mediaType, created, modified, data }.
Configuration
config passed to createAssetPickerApp:
| Key | Type | Default | Description |
|---|---|---|---|
storages |
object | – | The available storages. Each entry is passed to its adapter. |
storages.<id>.adapter |
string | – | Required. Adapter name (dummy, github, googledrive, entermediadb, or a registered custom one). |
storages.<id>.label |
string | id | Label in the sidebar / search results. |
storages.<id>.proxy |
bool | object | – | Per-storage proxy: true = use global proxy, false = disable, object = custom. |
pick.limit |
number | 1 |
Max assets that can be picked (0 = unlimited). |
pick.types |
string[] | ['file'] |
Asset types allowed to be picked (file, dir, category). |
pick.extensions |
string[] | [] |
Allowed file extensions (empty = all). |
proxy.url |
string | "proxy.php?to={{url}}" |
Proxy URL template; {{url}} = URL-encoded target, {{url.raw}} = raw. |
proxy.all |
bool | false |
Route all storages through the proxy unless a storage opts out. |
thumbnails |
'url' | 'data' |
'url' |
Deliver thumbnails as a URL, or fetch and inline as a data URI. |
language |
'auto' | 'en' | 'de' |
'auto' |
UI language; auto detects from the browser. |
Adapters
Built-in: dummy (random files, no auth — the demo), github, googledrive, entermediadb.
The three external adapters were modernized off dead auth APIs (GitHub removed the OAuth Authorizations API in 2020; Google shut down
gapi.auth2in 2023). Their response→item mapping is unit-tested; the live auth flows need real credentials to exercise end to end.
GitHub — github
Browses a repository's contents via the REST Contents API.
repo: { adapter: 'github', username: 'netresearch', repository: 'assetpicker', token: 'github_pat_…', // a Personal Access Token (fine-grained or classic), sent as a Bearer token }
The token may also be set globally as config.github.token. (A read-only PAT is required for private repos and to avoid the unauthenticated rate limit.)
Google Drive — googledrive
Lists Drive files via the Drive v3 API; authenticates with Google Identity Services (needs an OAuth client_id from a Google Cloud project with the Drive API enabled).
drive: { adapter: 'googledrive', client_id: 'xxx.apps.googleusercontent.com', api_key: 'xxx', }
EnterMediaDB — entermediadb
Searches assets via the mediadb services API; logs in through the built-in login form when the server requires it.
media: { adapter: 'entermediadb', url: 'https://em.example.org/openinstitute' }
EnterMediaDB has no CORS headers, so set proxy: true (or proxy.all) when your app is on a different origin — see PHP proxy.
Write your own adapter
An adapter is a factory returning { key, label, list, search }. Register it before creating the picker:
import { registerAdapter, createItem } from 'assetpicker'; registerAdapter('mysource', (storage, ctx) => ({ key: storage.key, label: storage.label, // item === null for the storage root; return { items, total } async list(item) { const rows = await fetchMyFolder(item ? item.id : '/'); const items = rows.map((row) => createItem({ id: row.id, storage: storage.key, name: row.name, type: row.isFolder ? 'dir' : 'file', links: { open: row.url }, data: row, }, ctx.thumbnails)); return { items, total: items.length }; }, async search(word) { /* … return { items, total } */ }, }));
ctx provides { onLoading, thumbnails, config }. Use the fetch client (createHttpClient) for the built-in proxy URL building, throttle and loading indicator.
PHP proxy
proxy.php (+ src/php/) is an optional PHP reverse proxy for CORS-restricted storages (built on symfony/http-client). Install its dependencies and run it behind your app:
composer install
A container setup is provided — build the image with Docker Bake and run it with Compose:
docker buildx bake # build the php-fpm image docker compose up -d # nginx + php serving proxy.php
Development
- App source:
app/src/(SFCs, models, adapters, i18n, http). - Tests:
app/tests/(Vitest + @vue/test-utils) —bun run test. - The demo (
app/index.html+app/src/main.js) is deployed to GitHub Pages from CI (.github/workflows/pages.yml); the build output is not committed.
License
MIT · © Netresearch DTT GmbH