ASCIInator/src/components/options/Jp2aOptions.vue
dev e904cdb3cb feat: full GUI, test API, auto-convert, aspect-ratio sliders
- Auto-convert on image load (0ms) and flag change (400ms debounce)
- SizeControl: linked width/height sliders with aspect-ratio lock and 0.5 font correction factor
- Full flag exposure for all 4 tools (chafa, jp2a, ascii-image-converter, img2txt)
- ChafaOptions: symbols/fill dropdowns, dither controls, work/threshold/font-ratio sliders, format select, toggles
- Jp2aOptions: color-depth, RGB weight sliders, edge controls, 8 toggles
- ImgTxtOptions: dither select with valid libcaca values, gamma slider
- OutputDisplay: ansi-to-html rendering for colored chafa output
- ShellBridge: abort-previous pattern, conversion-start/end lifecycle events
- Test API (ENABLE_TEST_API=true): /test/health, /test/convert, /test/flags/:tool, /test/imagemagick
- buildArgs: space-separated args (not = format); full schemas in SCHEMAS export
- runChafa: width/height destructured and combined into --size WxH
- Port changed to 3050; Vite on 0.0.0.0 with allowedHosts for production domain
- 98 unit tests passing across 12 test files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:29:45 +00:00

121 lines
4.7 KiB
Vue

<script setup>
import SizeControl from '../SizeControl.vue'
const props = defineProps({
flags: { type: Object, required: true },
imageDims: { type: Object, default: null },
})
const emit = defineEmits(['update:flags'])
const set = (key, val) => emit('update:flags', { ...props.flags, [key]: val })
const setNum = (key, val) => set(key, val === '' ? '' : Number(val))
const setBool = (key, val) => set(key, val)
</script>
<template>
<div class="flex flex-col gap-5">
<!-- Size -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">Size</p>
<SizeControl
:width="flags.width" :height="flags.height" :image-dims="imageDims"
@update:width="setNum('width', $event)"
@update:height="setNum('height', $event)"
/>
</div>
<!-- Color -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">Color</p>
<div class="grid grid-cols-2 gap-3">
<label class="ascii-label">
Color depth
<select class="ascii-input" :value="flags['color-depth']" @change="set('color-depth', $event.target.value)">
<option value="">default</option>
<option value="4">4 ANSI</option>
<option value="8">8 256 palette</option>
<option value="24">24 truecolor</option>
</select>
</label>
<label class="ascii-label">
Background
<select class="ascii-input" :value="flags.background" @change="set('background', $event.target.value)">
<option value="">default</option>
<option>light</option>
<option>dark</option>
</select>
</label>
</div>
</div>
<!-- RGB channel weights -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">RGB weights</p>
<div class="flex flex-col gap-3">
<label class="ascii-label">
Red {{ flags.red !== '' && flags.red !== undefined ? Number(flags.red).toFixed(3) : '0.299' }}
<input type="range" min="0" max="1" step="0.001"
:value="flags.red ?? 0.2989"
@input="set('red', $event.target.value)"
class="w-full" style="accent-color: #ff4444" />
</label>
<label class="ascii-label">
Green {{ flags.green !== '' && flags.green !== undefined ? Number(flags.green).toFixed(3) : '0.587' }}
<input type="range" min="0" max="1" step="0.001"
:value="flags.green ?? 0.5866"
@input="set('green', $event.target.value)"
class="w-full" style="accent-color: #39ff14" />
</label>
<label class="ascii-label">
Blue {{ flags.blue !== '' && flags.blue !== undefined ? Number(flags.blue).toFixed(3) : '0.114' }}
<input type="range" min="0" max="1" step="0.001"
:value="flags.blue ?? 0.1145"
@input="set('blue', $event.target.value)"
class="w-full" style="accent-color: #4488ff" />
</label>
</div>
</div>
<!-- Edge detection -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">Edge detection</p>
<label class="ascii-label">
Edge threshold {{ flags['edge-threshold'] !== '' && flags['edge-threshold'] !== undefined ? Number(flags['edge-threshold']).toFixed(2) : 'off' }}
<input type="range" min="0" max="1" step="0.01"
:value="flags['edge-threshold'] ?? 0"
@input="set('edge-threshold', $event.target.value)"
class="w-full" style="accent-color: var(--ascii-green)" />
</label>
</div>
<!-- Characters -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">Characters</p>
<label class="ascii-label">
Char palette
<input class="ascii-input" type="text" :value="flags.chars"
@input="set('chars', $event.target.value)" placeholder="e.g. .:-=+*#%@" />
</label>
</div>
<!-- Toggles -->
<div>
<p class="text-xs uppercase tracking-wider mb-2" style="color: rgba(224,224,224,.35)">Options</p>
<div class="flex flex-wrap gap-3">
<label v-for="opt in ['colors', 'fill', 'grayscale', 'invert', 'border', 'flipx', 'flipy', 'edges-only']"
:key="opt"
class="flex items-center gap-2 cursor-pointer select-none text-sm"
style="color: var(--ascii-text)">
<input type="checkbox"
:checked="!!flags[opt]"
@change="setBool(opt, $event.target.checked)"
class="w-4 h-4" style="accent-color: var(--ascii-green)" />
{{ opt }}
</label>
</div>
</div>
</div>
</template>