Skip to content

Commit

Permalink
feat: first setup
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Erslev Milfred committed Mar 20, 2023
0 parents commit 9caedc7
Show file tree
Hide file tree
Showing 14 changed files with 7,240 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
35 changes: 35 additions & 0 deletions .github/workflows/release-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
- run: npm ci
- run: npm test

publish-gpr:
needs: build
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://npm.pkg.github.com/
- run: npm install
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
node_modules
*.log
.nuxt
nuxt.d.ts
.output
.env
package-lock.json
framework
dist
.DS_Store

# Yarn
.yarn/cache
.yarn/*state*

# Local History
.history

# VSCode
.vscode/
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@limbo-works:registry=https://npm.pkg.github.com

shamefully-hoist=true
strict-peer-dependencies=false
16 changes: 16 additions & 0 deletions .playground/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div>
<BaseImg
fit="cover"
width="400"
height="200"
ratio="0.5"
class="w-auto h-[400px]"
src="/media/dj2bhlba/_hyt6524.jpg?rxy=0.5,0.5&width=2044&height=3072&rnd=133222323420230000"
:modifiers="{ fish: 'png' }"
@click="() => {}"
@click.native="() => {}"
/>
</div>
</template>

22 changes: 22 additions & 0 deletions .playground/assets/js/breakpoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const rootFontSize = 16;

/*
Note that we purposefully exclude teh 1440
breakpoint per default. A lot of screens are
this exact size, and having a lit of things
change exactly on that breakpoint can make
for some confusion. If you do need it for
something, you are of course free to un-
comment it.
*/
module.exports = [568, 605, 760, 990].reduce(
(acc, value) => ({
...acc,

[`${value}`]: {
px: value,
em: value / rootFontSize,
},
}),
{}
);
112 changes: 112 additions & 0 deletions .playground/components/BaseImg.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<script>
import { h } from 'vue';
import NuxtPictureExt from '../../components/NuxtPictureExt.vue';
export default defineComponent({
name: 'BaseImg',
components: {
NuxtPictureExt,
},
props: {
alt: {
type: String,
default: '',
},
loadColor: {
type: String,
default: '#fff',
},
quality: {
type: [Number, String],
default: 90,
},
},
render() {
const {
class: dynamicClass = null,
style: dynamicStyle = null,
} = this.$attrs;
const { fit, width, loadColor } = { ...this.$props, ...this.$attrs };
let placeholderStyles = null;
if (loadColor) {
let { height, ratio } = { ...this.$props, ...this.$attrs };
if (ratio) {
height = Math.round(width / ratio);
} else {
ratio = `${width} / ${height}`;
}
const placeholderSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="${loadColor}" /></svg>`;
const backgroundImage = `url("data:image/svg+xml,${encodeURIComponent(
placeholderSvg
)}")`;
placeholderStyles =
fit === 'contain'
? {
'--background-image': backgroundImage,
'--background-size': fit,
'--background-position': '50% 50%',
}
: {
'--background-color': loadColor,
'--background-size': '100% 100%',
};
}
const bindings = {
...this.$attrs,
class: ['c-base-img', dynamicClass].filter(Boolean),
style: [placeholderStyles, dynamicStyle].filter(
Boolean
),
alt: this.$props.alt || '',
quality: this.$props.quality || 100,
};
return h(NuxtPictureExt, bindings);
},
});
</script>

<style>
.c-base-img {
position: relative;
isolation: isolate;
}
.c-base-img img,
.c-base-img:before {
transition: opacity 0.3s;
}
.c-base-img:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0;
/* placeholder */
background-color: var(--background-color);
background-image: var(--background-image);
background-size: var(--background-size);
background-position: var(--background-position);
background-repeat: no-repeat;
}
.c-base-img.c-nuxt-picture-ext--is-loading img {
opacity: 0;
}
.c-base-img.c-nuxt-picture-ext--is-loading:before {
opacity: 1;
}
</style>
3 changes: 3 additions & 0 deletions .playground/nuxt.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default defineNuxtConfig({
extends: "..",
});
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Limbo.Nuxt.Image

Adds the `@nuxt/image-edge` module but configured to utilize the image processor used by our Umbraco backend ([ImageSharp](https://docs.sixlabors.com/api/ImageSharp/SixLabors.ImageSharp.html)). Also adds extended components expanding the ones provided by `@nuxt/image-edge`:

* `NuxtPictureExt`
* (`NuxtImgExt` could be added as well down the line)

These components should for the most part work as their counter-components with a few added nicities to match our setup.
So the [`@nuxt/image-edge` documentation](https://image.nuxtjs.org/components/nuxt-picture) should be highly usable.

## `NuxtPictureExt`

Supports the base functionality of `NuxtPicture` so generally follow its [documentation](https://image.nuxtjs.org/components/nuxt-picture). It also adds classes to the component following our base naming styles (`c-nuxt-picture-ext`, `c-nuxt-picture-ext--is-loading`, `c-nuxt-picture-ext--is-loaded` and `c-nuxt-picture-ext__img`) which can be used for styling purposes. Further, and through these classes and inline styles, some basis styles are added including:

* Aspect ratio when it is needed
* Automatically setting the right `object-fit` on the image
* `object-position` on the image when a `rxy` value is set in the src url params

148 changes: 148 additions & 0 deletions components/NuxtPictureExt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<template>
<NuxtPicture
v-if="src"
ref="NuxtPicture"
class="c-nuxt-picture-ext"
:class="`c-nuxt-picture-ext--${isLoaded ? 'is-loaded' : 'is-loading'}`"
:style="computedStyle"
:fit="fit"
:src="src"
:alt="alt"
:sizes="sizes"
:width="width"
:height="height"
:modifiers="{
ratio: ratio,
sourceWidth: width,
sourceHeight: height,
...modifiers,
}"
:img-attrs="computedImgAttrs"
:quality="quality"
:loading="loading"
@load="onLoad"
/>
</template>

<script>
// https://image.nuxtjs.org/components/nuxt-picture and https://image.nuxtjs.org/components/nuxt-img
export default defineComponent({
name: 'NuxtPictureExt',
props: {
alt: {
type: String,
default: '',
},
src: {
type: String,
},
sizes: {
type: String,
default: null,
},
width: { type: [Number, String], required: false },
height: { type: [Number, String], required: false },
ratio: { type: [Number, String], required: false },
fit: { type: String, default: '' },
loading: {
type: String,
default: 'lazy',
validator: (value) => ['lazy', 'eager', 'auto'].includes(value),
},
imgAttrs: { type: Object, default: () => ({}) },
quality: { type: [Number, String], default: 100 },
modifiers: { type: Object, default: () => ({}) },
},
data() {
return {
isLoaded: false,
};
},
computed: {
urlParams() {
const obj = {};
if (this.src) {
const url = new URL(this.src, 'https://example.com');
const params = new URLSearchParams(url.search);
params.forEach((value, key) => {
obj[key] = value;
});
}
return obj;
},
computedStyle() {
let style = null;
// Aspect ratio
if (this.ratio) {
style = { aspectRatio: this.ratio };
} else if (this.width && this.height) {
style = { aspectRatio: `${this.width} / ${this.height}` };
} else if (this.urlParams.width && this.urlParams.height) {
style = { aspectRatio: `${this.urlParams.width} / ${this.urlParams.height}` };
}
// Focus point
if (this.fit && ['cover', 'none'].includes(this.fit) && this.urlParams.rxy?.split?.(',').length === 2) {
const [x, y] = this.urlParams.rxy.split(',');
style = {
...style,
'--object-position': `${Math.round(x * 10000) / 100}% ${Math.round(y * 10000) / 100}%`,
};
}
return style;
},
computedImgAttrs() {
const className = ['c-nuxt-picture-ext__img', this.imgAttrs.class];
const style = [this.imgAttrs.style];
if (this.fit && this.fit !== 'crop') {
style.unshift({ objectFit: this.fit });
}
return {
...this.imgAttrs,
class: className.filter(Boolean),
style: style.filter(Boolean),
};
},
},
mounted() {
const image = this.$el?.querySelector?.('img');
if (image) {
this.isLoaded = image.complete && image.naturalHeight !== 0;
}
},
methods: {
onLoad(e) {
this.$emit('load', e);
this.isLoaded = true;
},
},
});
</script>
<style>
:where(.c-nuxt-picture-ext) {
display: inline-block;
width: auto;
height: auto;
}
:where(.c-nuxt-picture-ext__img) {
width: 100%;
height: 100%;
object-position: var(--object-position);
}
</style>
Loading

0 comments on commit 9caedc7

Please sign in to comment.