133 lines
4.0 KiB
Vue
133 lines
4.0 KiB
Vue
<script setup lang="ts">
|
||
import { t } from '@/i18n'
|
||
|
||
import { computed, ref } from 'vue'
|
||
import ImageCompare from '@/components/variants/ImageCompare.vue'
|
||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||
import { useVariantRender } from '@/composables/useVariantRender'
|
||
import {
|
||
SAMPLE_SOURCE,
|
||
estimateLabel,
|
||
estimateVariant,
|
||
type EstimateRequest,
|
||
} from '@/lib/variants/variantEstimate'
|
||
import { parseDimension, sliderValue, type VariantFormValues } from '@/lib/variants/variantForm'
|
||
import { formatVariantSize } from '@/lib/variants/variantFormat'
|
||
|
||
const URL_PREFIX = '/static/uploadedImages/'
|
||
const NAME_PLACEHOLDER = '<name>'
|
||
|
||
const props = defineProps<{
|
||
values: VariantFormValues
|
||
disabled: boolean
|
||
}>()
|
||
|
||
const position = ref(50)
|
||
const { copied, copy } = useCopyToClipboard()
|
||
|
||
const dimensionsAreValid = computed(
|
||
() =>
|
||
(props.values.width.trim() === '' || parseDimension(props.values.width) !== null) &&
|
||
(props.values.height.trim() === '' || parseDimension(props.values.height) !== null),
|
||
)
|
||
|
||
const request = computed<EstimateRequest | null>(() =>
|
||
dimensionsAreValid.value
|
||
? {
|
||
width: parseDimension(props.values.width),
|
||
height: parseDimension(props.values.height),
|
||
fit: props.values.fit,
|
||
format: props.values.format,
|
||
quality: sliderValue(props.values.quality),
|
||
}
|
||
: null,
|
||
)
|
||
|
||
const estimate = computed(() => (request.value ? estimateVariant(request.value) : null))
|
||
const { original, variant } = useVariantRender(request)
|
||
|
||
const shownName = computed(() => props.values.name || t('name'))
|
||
const outputLine = computed(() => {
|
||
if (!estimate.value) {
|
||
return t('No output yet')
|
||
}
|
||
const bytes = variant.value
|
||
? Math.min(SAMPLE_SOURCE.bytes, variant.value.bytes)
|
||
: estimate.value.bytes
|
||
return estimateLabel({ size: estimate.value.size, bytes })
|
||
})
|
||
const originalLine = computed(
|
||
() =>
|
||
`${SAMPLE_SOURCE.width} × ${SAMPLE_SOURCE.height} · ${formatVariantSize(SAMPLE_SOURCE.bytes)}`,
|
||
)
|
||
const hasName = computed(() => props.values.name !== '')
|
||
const url = computed(
|
||
() => `${URL_PREFIX}${SAMPLE_SOURCE.filename}?v=${props.values.name || NAME_PLACEHOLDER}`,
|
||
)
|
||
|
||
async function onCopy() {
|
||
try {
|
||
await copy(url.value)
|
||
} catch {
|
||
return
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex min-w-0 flex-col gap-3">
|
||
<div class="flex items-baseline gap-2">
|
||
<h3 class="flex-1 font-semibold text-highlighted">{{ t('Preview') }}</h3>
|
||
<span class="text-xs/5 text-muted">{{
|
||
t('Sample: {0}', [SAMPLE_SOURCE.filename])
|
||
}}</span>
|
||
</div>
|
||
<ImageCompare
|
||
v-model="position"
|
||
:name="values.name"
|
||
:comparable="estimate !== null"
|
||
:original-src="original"
|
||
:variant-src="variant?.url ?? null"
|
||
/>
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<div class="flex flex-col">
|
||
<span class="font-medium text-highlighted">{{ t('Original') }}</span>
|
||
<span class="text-xs/5 text-muted">{{ originalLine }}</span>
|
||
</div>
|
||
<div class="flex flex-col">
|
||
<span class="font-mono font-medium text-highlighted">{{ shownName }}</span>
|
||
<span class="text-xs/5 text-muted">{{ outputLine }}</span>
|
||
</div>
|
||
</div>
|
||
<p class="text-xs/5 text-muted">
|
||
{{
|
||
t(
|
||
'Drag the divider, or focus it and use the arrow keys. The estimate comes from a sample; output is never larger than the source.',
|
||
)
|
||
}}
|
||
</p>
|
||
<div class="flex flex-col gap-1.5">
|
||
<span class="font-medium text-highlighted">{{ t('URL') }}</span>
|
||
<div class="flex gap-2">
|
||
<div
|
||
data-testid="variant-url"
|
||
class="flex min-h-11 min-w-0 flex-1 items-center truncate border border-default bg-elevated px-3 font-mono text-xs sm:min-h-8"
|
||
:class="hasName ? 'text-highlighted' : 'text-muted'"
|
||
>
|
||
{{ url }}
|
||
</div>
|
||
<UButton
|
||
color="neutral"
|
||
variant="outline"
|
||
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
||
:disabled="!hasName || disabled"
|
||
class="h-11 shrink-0 gap-1.5 border border-accented bg-default px-3 text-sm text-highlighted ring-0 sm:h-8"
|
||
@click="onCopy"
|
||
>
|
||
{{ copied ? t('Copied') : t('Copy') }}
|
||
</UButton>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|