Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 75 additions & 27 deletions docs/content/3.providers/directus.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ links:
size: xs
---

Integration between [Directus](https://directus.io) and the image module.

To use this provider you just need to specify the base URL of your project.
To use this provider you only need to specify the base URL of your Directus instance.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
Expand All @@ -23,51 +21,101 @@ export default defineNuxtConfig({
})
```

You can easily override default options:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
image: {
directus: {
baseURL: 'http://mydirectus-domain.com/assets',
modifiers: {
withoutEnlargement: 'true',
transforms: [['blur', 4], ['negate']]
}
}
}
})
```

## Modifiers
## Props

All the default modifiers from [Directus documentation](https://docs.directus.io/reference/files.html#requesting-a-thumbnail) are available.
The NuxtImg Props map cleanly to the Directus Transforms.

```vue
<NuxtImg
provider="directus"
src="ad514db1-eb90-4523-8183-46781437e7ee"
height="512"
width="200"
fit="inside"
quality="20"
:modifiers="{ withoutEnlargement: 'true' }"
/>
```

Since Directus exposes the full [sharp API](https://sharp.pixelplumbing.com/api-operation) through the `transforms` parameter, we can use it inside our `modifiers` prop:
### Modifiers

The `modifiers` object is used for Directus specific features. All modifiers are optional.

#### Examples of Modifiers

::tabs{.w-full}
:::tabs-item{label="Modifiers"}

```vue
<NuxtImg
provider="directus"
src="ad514db1-eb90-4523-8183-46781437e7ee"
:modifiers="{
height: '512',
withoutEnlargement: 'true',
transforms: [['blur', 4], ['negate']]
}"
/>
```

::note
Note that the `transforms` parameter, as stated in the [Directus documentation](https://docs.directus.io/reference/files.html#advanced-transformations), is basically a list of lists. This is to facilitate the use of those sharp functions, like [`normalise`](https://sharp.pixelplumbing.com/api-operation#normalise), that would need multiple values in input: `transforms: [['normalise', 1, 99], ['blur', 4], ['negate']]`.
:::

:::tabs-item{label="Keyed Modifier"}

```vue
<NuxtImg
provider="directus"
:modifiers="{
key: "system-large-cover"
}"
/>
```

:::
::

#### All Modifiers

::field-group

::field{name="withoutEnlargement" type="boolean"}
Disable automatically upscaling the image when true.
::

::field{name="transforms" type="['string', ...any][]"}
A pipeline of transforms to tell Directus how to modify the image before sending.
:::collapsible

| **[Sharp Operation](https://sharp.pixelplumbing.com/api-operation/)**| **Options**| **Example Usage**|
|----------------------|--------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
| **rotate**| \[angle?: number, options?: \{ background: Color \} \]| \['rotate', 90\] or \['rotate', 90, \{ background: 'white' \} \]|
| **flip**| \[]| \['flip'\]|
| **flop**| \[]| \['flop'\]|
| **sharpen**| \[sigma?: number\] \| \[options?: \{ sigma?: number, m1?: number, m2?: number, x1?: number, y2?: number, y3?: number \} \]| \['sharpen', 1.5\] or \['sharpen', \{ sigma: 1.5, m1: 0.5 \} \]|
| **median**| \[size?: number\]| \['median', 5\]|
| **blur**| \[sigma?: number\] \| \[options?: \{ sigma?: number, precision?: 'integer' \| 'float' \| 'approximate', minAmplitude?: number \} \]| \['blur', 2\] or \['blur', \{ sigma: 2, precision: 'float' \} \]|
| **flatten**| \[options?: \{ background: Color \} \]| \['flatten', \{ background: 'black' \} \]|
| **unflatten**| \[]| \['unflatten'\]|
| **gamma**| \[gamma?: number, gammaOut?: number\]| \['gamma', 2.2\] or \['gamma', 2.2, 1.8\]|
| **negate**| \[options?: \{ alpha?: boolean \} \]| \['negate', \{ alpha: true \} \]|
| **normalize** or **normalise**| \[lower?: number, upper?: number\]| \['normalize', 0, 255\]|
| **clahe**| \[options?: \{ width: number, height: number, maxSlope?: number \} \]| \['clahe', \{ width: 8, height: 8 \} \]|
| **convolve**| \[kernel: \{ width: number, height: number, kernel: number\[], offset?: number \} \]| \['convolve', \{ width: 3, height: 3, kernel: [1, 1, 1, 1, 1, 1, 1, 1, 1], offset: 0 \} \]|
| **threshold**| \[value?: number, options?: \{ grayscale?: boolean \} \]| \['threshold', 128\] or \['threshold', 128, \{ grayscale: true \} \]|
| **linear**| \[a?: number \| number\[], b?: number \| number\[]\]| \['linear', 1.2, 0\] or \['linear', [1.2, 1.0], [0, 255]\]|
| **recomb**| \[matrix: number\[\]\[\]\]| \['recomb', \[\[0.5, 0.5, 0.5\], [0.5, 0.5, 0.5\], [0.5, 0.5, 0.5\]\] \]|
| **modulate**| \[options?: \{ brightness?: number, saturation?: number, hue?: number, lightness?: number \} \]| \['modulate', \{ brightness: 1.2, saturation: 1.5 \} \]|
| **tint**| \[color: Color\]| \['tint', 'red'\]|
| **grayscale** or **greyscale**| \[]| \['grayscale'\]|
| **pipelineColorspace** or **pipelineColourspace**| \[colorspace: SharpColorspace\]| \['pipelineColorspace', 'srgb'\]|
| **toColorspace** or **toColourspace**| \[colorspace: SharpColorspace\]| \['toColorspace', 'rgb'\]|
| **removeAlpha**| \[]| \['removeAlpha'\]|
| **ensureAlpha**| \[alpha?: number\]| \['ensureAlpha', 0.5\]|
| **extractChannel**| \[channel: 'red' \| 'green' \| 'blue' \| 'alpha'\]| \['extractChannel', 'red'\]|

:::

:::note
Directus defaults `ASSETS_TRANSFORM_MAX_OPERATIONS` to `5`. If you need more, it is recommended that you utilize a `key`ed transform. You can modify your [Directus Configuration](https://directus.io/docs/configuration/files#assets) to accommodate more transforms if necessary.
:::

::field{name="key" type="string"}
Sets a unique identifier for allowing faster and easier image transformation requests.
::
17 changes: 12 additions & 5 deletions playground/app/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1297,25 +1297,32 @@ export const providers: Provider[] = [
name: 'directus',
samples: [
{
src: 'ad514db1-eb90-4523-8183-46781437e7ee',
alt: 'Image 1',
src: '8f748634-d77b-4985-b27e-7e1f3559881a',
alt: 'Raw Image (no modifiers or transforms)',
},
{
src: 'ad514db1-eb90-4523-8183-46781437e7ee.jpg',
src: '8f748634-d77b-4985-b27e-7e1f3559881a.jpg',
alt: '1024px width',
width: 1024,
height: 256,
fit: 'cover',
modifiers: { withoutEnlargement: 'true' },
},
{
src: 'ad514db1-eb90-4523-8183-46781437e7ee',
src: '8f748634-d77b-4985-b27e-7e1f3559881a',
alt: '256px width, webp',
width: 256,
format: 'webp',
},
{
src: 'ad514db1-eb90-4523-8183-46781437e7ee',
src: '8f748634-d77b-4985-b27e-7e1f3559881a',
alt: 'keyed transform (all other inputs ignored)',
format: 'tiff',
modifiers: { key: 'system-large-cover' },
},

{
src: '8f748634-d77b-4985-b27e-7e1f3559881a',
alt: '256px width, webp',
width: 256,
format: 'webp',
Expand Down
2 changes: 1 addition & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default defineNuxtConfig({
apiVersion: 'v7',
},
directus: {
baseURL: 'http://localhost:8055/assets/',
baseURL: 'https://sandbox.directus.io/assets/',
},
fastly: {
baseURL: 'https://www.fastly.io',
Expand Down
136 changes: 121 additions & 15 deletions src/runtime/providers/directus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,135 @@ import { joinURL } from 'ufo'
import { createOperationsGenerator } from '../utils/index'
import { defineProvider } from '../utils/provider'

const operationsGenerator = createOperationsGenerator()
type SharpOperationMap = {
// Image Operations https://sharp.pixelplumbing.com/api-operation/
rotate: [angle?: number, options?: { background: Color }]
flip: []
flop: []
sharpen: [sigma?: number] | [options?: { sigma?: number, m1?: number, m2?: number, x1?: number, y2?: number, y3?: number }]
median: [size?: number]
blur: [sigma?: number] | [options?: { sigma?: number, precision?: 'integer' | 'float' | 'approximate', minAmplitude?: number }]
flatten: [options?: { background: Color }]
unflatten: []
gamma: [gamma?: number, gammaOut?: number]
negate: [options?: { alpha?: boolean }]
normalize: [lower?: number, upper?: number]
normalise: [lower?: number, upper?: number] // Alias
clahe: [options?: { width: number, height: number, maxSlope?: number }]
convolve: [kernel: { width: number, height: number, kernel: number[], offset?: number }]
threshold: [value?: number, options?: { grayscale?: boolean }]
linear: [a?: number | number[], b?: number | number[]]
recomb: [matrix: number[][]]
modulate: [options?: {
brightness?: number
saturation?: number
hue?: number
lightness?: number
}]

// Color Manipulation https://sharp.pixelplumbing.com/api-colour/
tint: [color: Color]
grayscale: []
greyscale: [] // Alias
pipelineColorspace: [colorspace: SharpColorspace]
pipelineColourspace: [colourspace: SharpColorspace] // Alias
toColorspace: [colorspace: SharpColorspace]
toColourspace: [colourspace: SharpColorspace] // Alias

// Channel Manipulation https://sharp.pixelplumbing.com/api-channel/
removeAlpha: []
ensureAlpha: [alpha?: number]
extractChannel: [channel: 'red' | 'green' | 'blue' | 'alpha'] // Restricting to string channel extraction to avoid complexity.
}

type SharpColorspace
= | 'srgb'
| 'rgb'
| 'scrgb'
| 'rgb16'
| 'cmyk'
| 'lab'
| 'b-w'
| string // retain advanced options for advanced users

type Color
= | string
| { r: number, g: number, b: number, alpha?: number }
| { h: number, s: number, l: number, alpha?: number }
| { h: number, s: number, v: number, alpha?: number }
| { c: number, m: number, y: number, k: number, alpha?: number }
| { h: number, w: number, b: number }
| { l: number, c: number, h: number }
| { l: number, a: number, b: number }
| { h: number, c: number, g: number }

type KnownSharpOperation
= {
[K in keyof SharpOperationMap]:
SharpOperationMap[K] extends []
? [K]
: [K, ...SharpOperationMap[K]]
}[keyof SharpOperationMap]

type CustomSharpOperation<K extends string = string>
= K extends keyof SharpOperationMap
? never
: [key: K, ...args: any[]]

type SharpOperation
= | KnownSharpOperation
| CustomSharpOperation

type DirectusModifiers
= | { key: string }
| {
key?: never
withoutEnlargement?: boolean
transforms?: SharpOperation[]
}

interface DirectusOptions {
baseURL: string
modifiers?: {
transforms?: string[]
withoutEnlargement?: boolean
}
width?: number
height?: number
quality?: number
format?: 'auto' | 'jpg' | 'png' | 'webp' | 'tiff' | 'avif'
fit?: 'cover' | 'contain' | 'inside' | 'outside' | 'fill'
modifiers?: DirectusModifiers
}

// HACK: See Discussion #2206
const operationsGenerator = createOperationsGenerator({
valueMap: {
transforms(value: SharpOperation[]) {
return value.length > 0
? JSON.stringify(
Array.from(new Set(value.map(v => JSON.stringify(v))))
.map(v => JSON.parse(v)),
)
: undefined
},
},
})

function isKeyModifier(
mod: DirectusModifiers | undefined,
): mod is { key: string } {
return !!mod && 'key' in mod && typeof mod.key === 'string'
}

export default defineProvider<DirectusOptions>({
getImage: (src, { modifiers, baseURL }) => {
// Separating the transforms from the rest of the modifiers
const transforms = modifiers.transforms && Array.isArray(modifiers.transforms) && modifiers.transforms.length > 0
? JSON.stringify(Array.from(new Set(modifiers.transforms.map(obj => JSON.stringify(obj)))).map(value => JSON.parse(value)))
: undefined

const operations = operationsGenerator({
...modifiers,
transforms,
})
if (isKeyModifier(modifiers)) {
return {
url: joinURL(baseURL, src + `?key=${modifiers.key}`),
}
}

const operations = operationsGenerator(modifiers)

return {
url: joinURL(baseURL, src + (operations ? ('?' + operations.replace(/=+$/, '')) : '')),
url: joinURL(baseURL, src + (operations ? `?${operations}` : '')),
}
},
})
18 changes: 10 additions & 8 deletions test/e2e/__snapshots__/directus.json5
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the tests specifically care about the order, nor did I do extensive research on why I needed to put the requests in a different order than the sources for a successful test. If I'm hiding a problem here, I'd like to know about it but as far as I can tell the order has no meaning outside of the test.

Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"requests": [
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee.jpg?withoutEnlargement=true&width=1024&height=256&fit=cover",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee?width=256&format=webp",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee?withoutEnlargement=true&transforms=%5B%5B%22blur%22%2C4%5D%2C%5B%22negate%22%5D%5D&width=256&format=webp",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a.jpg?withoutEnlargement=true&width=1024&height=256&fit=cover",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?key=system-large-cover",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?width=256&format=webp",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?withoutEnlargement=true&transforms=%5B%5B%22blur%22%2C4%5D%2C%5B%22negate%22%5D%5D&width=256&format=webp",
],
"sources": [
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee.jpg?withoutEnlargement=true&width=1024&height=256&fit=cover",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee?width=256&format=webp",
"http://localhost:8055/assets/ad514db1-eb90-4523-8183-46781437e7ee?withoutEnlargement=true&transforms=%5B%5B%22blur%22%2C4%5D%2C%5B%22negate%22%5D%5D&width=256&format=webp",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a.jpg?withoutEnlargement=true&width=1024&height=256&fit=cover",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?width=256&format=webp",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?key=system-large-cover",
"https://sandbox.directus.io/assets/8f748634-d77b-4985-b27e-7e1f3559881a?withoutEnlargement=true&transforms=%5B%5B%22blur%22%2C4%5D%2C%5B%22negate%22%5D%5D&width=256&format=webp",
],
}
Loading
Loading