-
Notifications
You must be signed in to change notification settings - Fork 324
feat(picsum): add Lorem Picsum placeholder image provider #2106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3ad71ef
0140c9f
17bc9a8
33ee77c
d4e6d37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| --- | ||
| title: Picsum | ||
| description: Nuxt Image with Lorem Picsum placeholder images. | ||
| links: | ||
| - label: Source | ||
| icon: i-simple-icons-github | ||
| to: https://github.com/nuxt/image/blob/main/src/runtime/providers/picsum.ts | ||
| size: xs | ||
| --- | ||
|
|
||
| Integration between [Lorem Picsum](https://picsum.photos/) and the image module. | ||
|
|
||
| Lorem Picsum provides random placeholder images. It is useful during development or for demo purposes. | ||
|
|
||
| To use this provider you just need to enable it in your Nuxt config: | ||
|
|
||
| ```ts [nuxt.config.ts] | ||
| export default defineNuxtConfig({ | ||
| image: { | ||
| picsum: {} | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Random image | ||
|
|
||
| Get a random image with specific dimensions: | ||
|
|
||
| ```vue | ||
| <NuxtImg | ||
| provider="picsum" | ||
| src="/" | ||
| width="200" | ||
| height="300" | ||
| /> | ||
| ``` | ||
|
|
||
| ### Specific image by ID | ||
|
|
||
| Use `id/{id}` as the `src` to get a specific image: | ||
|
|
||
| ```vue | ||
| <NuxtImg | ||
| provider="picsum" | ||
| src="id/237" | ||
| width="200" | ||
| height="300" | ||
| /> | ||
| ``` | ||
|
|
||
| ### Seeded random image | ||
|
|
||
| Use `seed/{seed}` as the `src` to get a consistent random image based on a seed string: | ||
|
|
||
| ```vue | ||
| <NuxtImg | ||
| provider="picsum" | ||
| src="seed/picsum" | ||
| width="200" | ||
| height="300" | ||
| /> | ||
| ``` | ||
|
|
||
| ## Modifiers | ||
|
|
||
| ### `grayscale` | ||
|
|
||
| Return the image in grayscale. | ||
|
|
||
| ```vue | ||
| <NuxtImg | ||
| provider="picsum" | ||
| src="id/870" | ||
| width="300" | ||
| height="200" | ||
| :modifiers="{ grayscale: true }" | ||
| /> | ||
| ``` | ||
|
|
||
| ### `blur` | ||
|
|
||
| Apply a blur effect to the image. Accepts values from 1 to 10. | ||
|
|
||
| ```vue | ||
| <NuxtImg | ||
| provider="picsum" | ||
| src="id/1025" | ||
| width="250" | ||
| height="250" | ||
| :modifiers="{ blur: 5 }" | ||
| /> | ||
| ``` | ||
|
|
||
| ::note | ||
| The `src` value must be either `id/{id}` for a specific image, `seed/{seed}` for a seeded random image, or `/` for a fully random image. Other `src` values are ignored and a random image will be returned. | ||
| :: | ||
|
|
||
| ::note | ||
| Picsum does not support height-only resizing. When only `height` is provided without `width`, a square image of that dimension will be returned. | ||
| :: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // https://picsum.photos/ - Lorem Picsum placeholder images | ||
|
|
||
| import { joinURL, withQuery } from 'ufo' | ||
| import { defineProvider } from '../utils/provider' | ||
|
|
||
| interface PicsumModifiers { | ||
| grayscale?: boolean | ||
| blur?: number | ||
| } | ||
|
|
||
| interface PicsumOptions { | ||
| baseURL?: string | ||
| modifiers?: PicsumModifiers | ||
| } | ||
|
|
||
| export const picsumCDN = 'https://picsum.photos/' | ||
|
|
||
| export default defineProvider<PicsumOptions>({ | ||
| getImage: (src, { modifiers, baseURL = picsumCDN }) => { | ||
| const { width, height, grayscale, blur, ...otherModifiers } = modifiers || {} | ||
|
|
||
| // Build the path | ||
| // Picsum URL format: https://picsum.photos/[id/{id}/]{width}[/{height}] | ||
| // Examples: | ||
| // - Random: https://picsum.photos/200/300 | ||
| // - Specific ID: https://picsum.photos/id/237/200/300 | ||
| // - Square: https://picsum.photos/200 | ||
|
|
||
| const parts: string[] = [] | ||
|
|
||
| // If src is provided and not empty, it could be: | ||
| // - "id/237" for a specific image | ||
| // - "seed/picsum" for a seeded image | ||
| if (src && src !== '/') { | ||
| const [type, id] = (src.startsWith('/') ? src.slice(1) : src).split('/') | ||
| if (type && (type === 'id' || type === 'seed')) { | ||
| parts.push(`${type}/${id}`) | ||
| } | ||
| } | ||
|
|
||
| // Add dimensions - these come after the ID/seed path | ||
| if (width) { | ||
| parts.push(String(width)) | ||
| } | ||
| if (height) { | ||
| parts.push(String(height)) | ||
| } | ||
|
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Height without width produces an ambiguous Picsum URL. When only Consider either: (a) documenting this as a known limitation, or (b) emitting both segments (e.g., using a default width or swapping to π€ Prompt for AI Agents |
||
|
|
||
| // Build query parameters for modifiers | ||
| const query: Record<string, string | number> = {} | ||
|
|
||
| if (grayscale) { | ||
| query.grayscale = '' | ||
| } | ||
|
|
||
| if (blur !== undefined && blur > 0) { | ||
| // Picsum blur accepts values from 1-10 | ||
| query.blur = Math.min(Math.max(Math.round(blur), 1), 10) | ||
| } | ||
|
|
||
| // Add any other custom modifiers (excluding standard ones that don't apply to picsum) | ||
| for (const [key, value] of Object.entries(otherModifiers)) { | ||
| if (value !== undefined && value !== null && !['fit', 'format', 'quality', 'background'].includes(key)) { | ||
| query[key] = value as string | number | ||
| } | ||
| } | ||
|
|
||
| const url = joinURL(baseURL, ...parts) | ||
|
|
||
| return { | ||
| url: Object.keys(query).length > 0 ? withQuery(url, query) : url, | ||
| } | ||
| }, | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "requests": [ | ||
| "https://picsum.photos/id/1025/250/250?blur=5", | ||
| "https://picsum.photos/id/237/200/300", | ||
| "https://picsum.photos/id/870/300/200?grayscale", | ||
| "https://picsum.photos/seed/picsum/200/200", | ||
| ], | ||
| "sources": [ | ||
| "https://picsum.photos/id/237/200/300", | ||
| "https://picsum.photos/id/870/300/200?grayscale", | ||
| "https://picsum.photos/id/1025/250/250?blur=5", | ||
| "https://picsum.photos/seed/picsum/200/200", | ||
| ], | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/auto_image/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net' }, | ||
| picsum: { url: 'https://picsum.photos/' }, | ||
| }, | ||
| { | ||
| args: ['/test.png', { width: 200 }], | ||
|
|
@@ -76,6 +77,7 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200/auto_image/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200' }, | ||
| picsum: { url: 'https://picsum.photos/200' }, | ||
| }, | ||
| { | ||
| args: ['/test.png', { height: 200 }], | ||
|
|
@@ -114,6 +116,7 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=height:200/auto_image/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?h=200' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?height=200' }, | ||
| picsum: { url: 'https://picsum.photos/200' }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Height-only requests produce a square image, not a height-constrained one. When only For a placeholder image provider this is likely acceptable, but it may be worth documenting this limitation or considering emitting π€ Prompt for AI Agents |
||
| }, | ||
| { | ||
| args: ['/test.png', { width: 200, height: 200 }], | ||
|
|
@@ -152,6 +155,7 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200/auto_image/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' }, | ||
| picsum: { url: 'https://picsum.photos/200/200' }, | ||
| }, | ||
| { | ||
| args: ['/test.png', { width: 200, height: 200, fit: 'contain' }], | ||
|
|
@@ -190,6 +194,7 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200,fit:max/auto_image/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' }, | ||
| picsum: { url: 'https://picsum.photos/200/200' }, | ||
| }, | ||
| { | ||
| args: ['/test.png', { width: 200, height: 200, fit: 'contain', format: 'jpeg' }], | ||
|
|
@@ -228,5 +233,6 @@ export const images = [ | |
| hygraph: { url: 'https://eu-central-1-shared-euc1-02.graphassets.com/cltsj3mii0pvd07vwb5cyh1ig/resize=width:200,height:200,fit:max/output=format:jpeg/cltsrex89477t08unlckqx9ue' }, | ||
| caisy: { url: 'https://assets.caisy.io/assets/b76210be-a043-4989-98df-ecaf6c6e68d8/056c27e2-81f5-4cd3-b728-cef181dfe7dc/d83ea6f0-f90a-462c-aebd-b8bc615fdce0pexelsmiguelapadrinan1591056.jpg?w=200&h=200' }, | ||
| bunny: { url: 'https://bunnyoptimizerdemo.b-cdn.net?width=200&height=200' }, | ||
| picsum: { url: 'https://picsum.photos/200/200' }, | ||
| }, | ||
| ] as const | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation:
idcan beundefinedwhen src is"id"or"seed".If
srcis"id"(without a trailing ID value),split('/')yields["id"], soidisundefined, andparts.push(\${type}/${id}`)produces"id/undefined"` in the URL.Proposed fix
if (src && src !== '/') { const [type, id] = (src.startsWith('/') ? src.slice(1) : src).split('/') - if (type && (type === 'id' || type === 'seed')) { + if (type && id && (type === 'id' || type === 'seed')) { parts.push(`${type}/${id}`) } }π Committable suggestion
π€ Prompt for AI Agents