264 lines
10 KiB
Vue
264 lines
10 KiB
Vue
<template>
|
|
<div class="max-w-md mx-auto w-full flex flex-col gap-4 lg:gap-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="heading">Heading</label>
|
|
<input type="text" class="mt-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="heading" id="heading">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="subheading">Subheading</label>
|
|
<textarea class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="subheading" id="subheading"></textarea>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="author">Author</label>
|
|
<input type="text" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="author" id="author">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="avatar">Avatar</label>
|
|
<input type="file" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-on:change="updateAvatar" id="avatar">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="logo">Logo</label>
|
|
<input type="file" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-on:change="updateLogo" id="logo">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="text-color">Text color</label>
|
|
<input type="color" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="textColor" id="text-color">
|
|
</div>
|
|
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="width">Size and quality</label>
|
|
|
|
<div class="flex justify-between">
|
|
<input type="number" class="mt-1 inline w-49 rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="width" id="width">
|
|
<input type="number" class="mt-1 inline w-48 rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="height">
|
|
</div>
|
|
|
|
<input type="range" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" min="0" max="1" step="0.1" v-model="quality">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="background">Background</label>
|
|
<input type="file" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-on:change="updateBackground" id="background">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="background-hover">Background hover</label>
|
|
<input type="color" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" v-model="backgroundHover" id="background-hover">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="background-hover-opacity">Background hover opacity</label>
|
|
<input type="range" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" min="0" max="1" step="0.1" v-model="backgroundHoverOpacity" id="background-hover-opacity">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="heading-size">Heading size</label>
|
|
<input type="range" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" min="1" max="10" step="0.1" v-model="headingSize" id="heading-size">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700" for="heading-size">Subheading size</label>
|
|
<input type="range" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" min="1" max="10" step="0.1" v-model="subheadingSize" id="subheading-size">
|
|
</div>
|
|
</div>
|
|
<div class="space-y-6">
|
|
<div class="og" ref="og" :style="ogStyle()">
|
|
<div class="og-image" :style="ogImageStyle()">
|
|
<div class="og-image-hover" :style="ogImageHoverStyle()">
|
|
<div class="h-full w-full px-10 flex flex-col justify-between">
|
|
<div>
|
|
<h1 class="pt-10 leading-none" v-text="heading" :style="ogHeadingStyle()"></h1>
|
|
<p class="mt-10 mb-16 leading-tight" v-text="subheading" :style="ogSubheadingStyle()"></p>
|
|
</div>
|
|
|
|
<div class="w-full flex flex-row items-center og-image--footer mb-10">
|
|
<img class="mr-4 h-10 w-10 rounded-full" v-if="avatar" :src="avatar">
|
|
|
|
<span class="mr-auto" v-text="author"></span>
|
|
|
|
<img v-if="logo" class="mr-4 h-10 w-10 rounded-full" :src="logo">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="inline-flex rounded-md shadow-sm" role="group">
|
|
<button type="button" class="px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-l-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-blue-500 dark:focus:text-white" v-on:click="downloadAsPng">
|
|
PNG
|
|
</button>
|
|
<button type="button" class="px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-r-md hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-blue-500 dark:focus:text-white" v-on:click="downloadAsJpeg">
|
|
JPEG
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.og {
|
|
background: #cecece;
|
|
font-family: Trebuchet MS;
|
|
}
|
|
|
|
.og-heading {
|
|
font-size: 5em;
|
|
}
|
|
|
|
.og-subheading {
|
|
font-size: 3em;
|
|
}
|
|
|
|
.og-image--footer {
|
|
font-size: 1.3em;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import { hexToRgb } from '../util/color'
|
|
import { toBase64 } from '../util/file'
|
|
import { toPng, toJpeg } from 'html-to-image'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
author: localStorage.getItem('author'),
|
|
heading: localStorage.getItem('heading'),
|
|
subheading: localStorage.getItem('subheading'),
|
|
avatar: localStorage.getItem('avatar'),
|
|
logo: localStorage.getItem('logo'),
|
|
textColor: localStorage.getItem('textColor') ?? '#33333',
|
|
background: localStorage.getItem('background'),
|
|
backgroundHover: localStorage.getItem('backgroundHover') ?? '#33333',
|
|
backgroundHoverOpacity: localStorage.getItem('backgroundHoverOpacity') ?? 0,
|
|
headingSize: localStorage.getItem('headingSize') ?? 4,
|
|
subheadingSize: localStorage.getItem('subheadingSize') ?? 2,
|
|
width: 1920,
|
|
height: 1080,
|
|
quality: 0.9,
|
|
}
|
|
},
|
|
methods: {
|
|
ogStyle() {
|
|
return {
|
|
color: this.textColor,
|
|
}
|
|
},
|
|
ogImageStyle() {
|
|
if (this.background) {
|
|
return {
|
|
background: `url(${this.background})`,
|
|
backgroundSize: 'cover',
|
|
}
|
|
}
|
|
},
|
|
ogHeadingStyle() {
|
|
return {
|
|
fontSize: `${this.headingSize}em`,
|
|
}
|
|
},
|
|
ogSubheadingStyle() {
|
|
return {
|
|
fontSize: `${this.subheadingSize}em`,
|
|
}
|
|
},
|
|
ogImageHoverStyle() {
|
|
let background = 'none'
|
|
|
|
if (this.backgroundHover) {
|
|
const colors = hexToRgb(this.backgroundHover)
|
|
background = `rgba(${colors.red}, ${colors.green}, ${colors.blue}, ${this.backgroundHoverOpacity})`
|
|
}
|
|
|
|
return {
|
|
background,
|
|
aspectRatio: `${this.width}/${this.height}`
|
|
}
|
|
},
|
|
async updateBackground(event) {
|
|
this.background = await this.convertFileToBase64(event.target.files)
|
|
},
|
|
async updateLogo(event) {
|
|
this.logo = await this.convertFileToBase64(event.target.files)
|
|
},
|
|
async updateAvatar(event) {
|
|
this.avatar = await this.convertFileToBase64(event.target.files)
|
|
},
|
|
downloadAsPng() {
|
|
return this.download(toPng, 'image.png')
|
|
},
|
|
downloadAsJpeg() {
|
|
return this.download(toJpeg, 'image.jpg')
|
|
},
|
|
download(callback, filename) {
|
|
const element = this.$refs.og
|
|
|
|
callback(this.$refs.og, {
|
|
canvasWidth: this.width,
|
|
canvasHeight: this.height,
|
|
quality: this.quality,
|
|
})
|
|
.then((dataUrl) => {
|
|
const link = document.createElement('a')
|
|
|
|
link.setAttribute('href', dataUrl)
|
|
link.setAttribute('download', filename)
|
|
link.click()
|
|
})
|
|
.catch((error) => {
|
|
element.classList.toggle('og--scale', false)
|
|
|
|
console.error('oops, something went wrong!', error);
|
|
})
|
|
},
|
|
async convertFileToBase64(files) {
|
|
if (files.length) {
|
|
return await toBase64(files[0])
|
|
}
|
|
|
|
return null
|
|
},
|
|
},
|
|
watch: {
|
|
author(value) {
|
|
localStorage.setItem('author', value)
|
|
},
|
|
heading(value) {
|
|
localStorage.setItem('heading', value)
|
|
},
|
|
subheading(value) {
|
|
localStorage.setItem('subheading', value)
|
|
},
|
|
avatar(value) {
|
|
localStorage.setItem('avatar', value)
|
|
},
|
|
logo(value) {
|
|
localStorage.setItem('logo', value)
|
|
},
|
|
textColor(value) {
|
|
localStorage.setItem('textColor', value)
|
|
},
|
|
background(value) {
|
|
localStorage.setItem('background', value)
|
|
},
|
|
backgroundHover(value) {
|
|
localStorage.setItem('backgroundHover', value)
|
|
},
|
|
backgroundHoverOpacity(value) {
|
|
localStorage.setItem('backgroundHoverOpacity', value)
|
|
},
|
|
headingSize(value) {
|
|
localStorage.setItem('headingSize', value)
|
|
},
|
|
subheadingSize(value) {
|
|
localStorage.setItem('subheadingSize', value)
|
|
},
|
|
}
|
|
}
|
|
</script>
|