You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

533 lines
15 KiB

<template>
<section
class="bg-gray-800 overflow-y-auto p-10 w-full"
id="settings"
ref="section"
>
<header class="flex items-center justify-between w-full">
<h1 class="font-medium text-2xl text-green">Options</h1>
<BtnClose @click.native="closeView" />
</header>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">General</h2>
<div class="flex items-center mr-6 my-2 px-4 text-gray-100">
<p class="mr-4" v-tooltip.right-end="'Image conversion file target.'">
Target
</p>
<Dropdown
:options="targets"
:selected="target"
class="m-0 text-gray-200"
v-on:update-option="selectTarget"
></Dropdown>
</div>
<div class="flex flex-wrap items-center mr-8 my-2 px-4 text-gray-100">
<p v-tooltip="'The output directory for converted images.'">
Destination
</p>
<p
@click="selectOutDir"
class="bg-gray-900 cursor-pointer font-mono hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
>
{{ config.outDir }}
</p>
<button
@click="openDir"
class="cursor-pointer"
v-tooltip.right-end="'Open destination directory'"
>
<svg
height="18"
id="Layer_1"
style="enable-background:new 0 0 24 24;"
version="1.1"
viewBox="0 0 24 24"
width="20"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
y="0px"
>
<path
d="M20,3H4C2.9,3,2,3.9,2,5v14c0,1.1,0.9,2,2,2h5v-2H4V7h16v12h-5v2h5c1.1,0,2-0.9,2-2V5C22,3.9,21.1,3,20,3z"
fill="#b3b3b3"
/>
<path d="M13,21v-5h3l-4-5l-4,5h3v5H13z" fill="#b3b3b3" />
</svg>
</button>
</div>
<div class="flex flex-wrap items-center my-2 px-4 text-gray-100">
<label
for="prefix"
v-tooltip.right-end="{
content: 'A string to prepend to the converted image filename.'
}"
>Prefix</label
>
<input
@change="setConfig"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
id="prefix"
maxlength="16"
type="text"
v-model="config.prefix"
/>
</div>
<div class="flex flex-wrap items-center my-2 px-4 text-gray-100">
<label
for="suffix"
v-tooltip.right-end="{
content: 'A string to append to the converted image filename.'
}"
>Suffix</label
>
<input
@change="setConfig"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
id="suffix"
maxlength="16"
type="text"
v-model="config.suffix"
/>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 px-2 py-4 rounded-md w-full"
>
<header class="flex px-2 w-full">
<h2 class="text-gray-200 text-xl">Resizing</h2>
<button
@click="addSize"
class="border-0 focus:outline-none hover:green hover:text-green ml-8 ta-slow"
v-tooltip.right-end="{
content: 'Add an image size to convert.',
delay: 600
}"
>
Add Size +
</button>
</header>
<div
:key="i"
class="flex flex-wrap items-center my-2 px-4 text-gray-100 w-full"
v-for="(s, i) in config.sizes"
>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label :for="`width-${i}`" class="block xl:inline-block mb-2 mr-4"
><span
v-tooltip.right-end="
'Width in pixels. Required and must be a positive integer.'
"
>Width</span
></label
>
<input
:id="`width-${i}`"
@blur="setConfig"
@input="handleNumber"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green px-4 py-2 rounded-md ta-color-slow w-full lg:w-auto"
type="text"
v-model.number="s.width"
/>
</div>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label :for="`height-${i}`" class="block xl:inline-block mb-2 mr-4"
><span
v-tooltip.right-end="
'Height in pixels. Required and must be a positive integer.'
"
>Height</span
></label
>
<input
:id="`height-${i}`"
@blur="setConfig"
@input="handleNumber"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green px-4 py-2 rounded-md ta-color-slow w-full lg:w-auto"
type="text"
v-model.number="s.height"
/>
</div>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label class="block xl:inline-block mb-2 mr-4">Strategy</label>
<div class="flex lg:inline-flex w-full lg:w-auto">
<Dropdown
:options="strategies"
:selected="strategy(s)"
@click.native="handleSelectStrategy(i)"
class="my-0 mr-4 text-gray-200 w-full"
v-on:update-option="selectStrategy"
v-tooltip="strategyTooltip(s)"
></Dropdown>
<button @click="removeSize(i)">
<svg
height="10"
id="x"
style="enable-background:new 0 0 11.9 11.9;"
version="1.1"
viewBox="0 0 11.9 11.9"
width="10"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
y="0px"
>
<path
d="M10.4,0L6,4.5L1.5,0L0,1.5L4.5,6L0,10.4l1.5,1.5L6,7.5l4.5,4.5l1.5-1.5L7.5,6l4.5-4.5L10.4,0z"
fill="#b3b3b3"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">WebP</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-6" v-tooltip.right-end="{ content: tooltips.quality }">
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.webpOpt.quality" />
</div>
</div>
</div>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-4">Lossless</p>
<div
@click="toggleWebpLossless"
class="bg-gray-900 check-wrapper flex items-center justify-center rounded-md"
>
<transition mode="out-in" name="fade">
<svg
height="24"
id="check-icon"
style="enable-background:new 0 0 24 24;"
v-if="config.webpOpt.lossless"
version="1.1"
viewBox="0 0 24 24"
width="24"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
y="0px"
>
<path
d="M10,15.6l-3.3-3.3l-1.4,1.4l4.7,4.7l9.7-9.7l-1.4-1.4L10,15.6z"
fill="#27ffa7"
/>
</svg>
</transition>
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">JPEG</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-6" v-tooltip.right-end="{ content: tooltips.quality }">
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.jpegOpt.quality" />
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">PNG</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p
class="mr-6"
title="Specify the image conversion quality."
v-tooltip.right-end="{ content: tooltips.quality }"
>
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.pngOpt.quality" />
</div>
</div>
</div>
</div>
<div class="mb-4 w-full">
<button
@click="restoreDefaults"
class="border-gray-400 btn focus:outline-none hover:bg-green hover:border-green hover:text-gray-900 ml-auto ta-slow"
>
Restore Defaults
</button>
</div>
</section>
</template>
<script>
import BtnClose from './BtnClose'
import Dropdown from './Dropdown'
import VueSlider from 'vue-slider-component'
import 'vue-slider-component/theme/antd.css'
import { EventBus } from '@/lib/event-bus'
export default {
name: 'Settings',
components: { BtnClose, Dropdown, VueSlider },
data() {
return {
activeStrategy: 0,
strategies: [
{ name: 'Fill', value: 0 },
{ name: 'Fit', value: 1 },
{ name: 'Smart Crop', value: 2 }
],
targets: [
{ name: 'WebP', value: 'webp' },
{
name: 'JPEG',
value: 'jpg'
},
{ name: 'PNG', value: 'png' }
],
tooltips: {
quality:
'Image quality of the converted image where 0 is the lowest and 100 is the highest.'
}
}
},
computed: {
/**
* config returns the app configuration from state.
* @returns {object}
*/
config() {
return this.$store.getters.config
},
target() {
return (
this.targets.find(t => this.config.target === t.value) || {
name: '',
value: ''
}
)
}
},
methods: {
/**
* addSize adds a blank size to the size config.
*/
addSize() {
this.$store.dispatch('addSize')
},
/**
* removeSize removes the selected size rect from the sizes config.
* @param {number} i - The size index.
*/
removeSize(i) {
this.$store.dispatch('removeSize', i)
this.setConfig()
},
/**
* handleSelectStrategy records the index of the currently active size input.
* @param {number} i - The size index.
*/
handleSelectStrategy(i) {
this.activeStrategy = i
},
/**
* selectStrategy updates the configured size with the selected strategy.
* @param {object} e - The selected strategy.
*/
selectStrategy(e) {
this.$store
.dispatch('setSizeStrategy', {
index: this.activeStrategy,
value: e.value
})
.then(() => {
this.setConfig()
})
.catch(err => {
console.log(err)
})
},
/**
* strategy returns the strategy from the list of strategies matching the
* current strategy.
* @param {object} s - The current size.
* @returns {object} The matching strategy.
*/
strategy(s) {
return this.strategies.find(o => s.strategy === o.value)
},
/**
* strategyTooltip returns a tooltip content string based on the selected
* strategy.
* @param {object} s - Current size.
* @returns string The tooltip content.
*/
strategyTooltip(s) {
switch (s.strategy) {
case 0: {
return 'Crop resizes the image to the specified dimensions, cropping excess from the center.'
}
case 1: {
return "Fit resizes the image to fit within the specified dimensions, maintaining the image's aspect ratio."
}
default: {
return ''
}
}
},
/**
* closeView closes the current view.
*/
closeView() {
this.$emit('close-view')
},
/**
* handleNumber validates a number input and displays an error message if
* the input cannot be parsed.
* @param {InputEvent} e
*/
handleNumber(e) {
if (!e.data) return
const n = parseInt(e.data, 10)
if (isNaN(n)) {
EventBus.$emit('notify', {
msg: `Image size must be a number.`,
type: 'warn'
})
}
},
/**
* openDir opens the configured output directory.
*/
openDir() {
window.backend.Config.OpenOutputDir()
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
},
/**
* restoreDefaults resets the app configuration to defaults.
*/
restoreDefaults() {
window.backend.Config.RestoreDefaults()
.then(() => {
this.$store.dispatch('getConfig')
})
.catch(err => {
console.error(err)
})
},
/**
* selectOutDir selects an output directory in the config.
*/
selectOutDir() {
window.backend.Config.SetOutDir()
.then(res => {
console.log(res)
this.$store.dispatch('getConfig')
})
.catch(err => {
console.error(err)
})
},
/**
* selectTarget updates the config target.
* @param {object} e - The selected target.
*/
selectTarget(e) {
this.$store
.dispatch('setConfigProp', { key: 'target', value: e.value })
.then(() => {
this.setConfig()
})
.catch(err => {
console.error(err)
})
},
/**
* setConfig updates the configuration with the current state.
*/
setConfig() {
this.$store.dispatch('setConfig', this.config)
},
/**
* toggleWebpLossless toggles the lossless property of the WebP config.
*/
toggleWebpLossless() {
this.$store
.dispatch('toggleWebpLossless')
.then(() => {
this.setConfig()
})
.catch(err => {
console.error(err)
})
}
}
}
</script>
<style scoped>
input:focus {
color: #27ffa7;
}
button > svg > path {
transition: fill 0.6s cubic-bezier(0.07, 0.95, 0, 1);
}
button:hover > svg > path {
fill: #27ffa7;
}
.check-wrapper {
border: 2px solid transparent;
cursor: pointer;
width: 24px;
height: 24px;
}
</style>