feat(web): сворачивание блока «Кривая» в инструменте «Свет»

Заголовок «Кривая» стал кнопкой со стрелкой: график с гистограммой и точками
сворачивается и разворачивается, кнопки выбора канала остаются видны всегда,
чтобы канал можно было переключить и в свёрнутом виде.
This commit is contained in:
iu
2026-10-21 20:00:30 +03:00
parent 48ed0bb775
commit 332f35b553
@@ -30,6 +30,7 @@
let busy = $state(false); let busy = $state(false);
let error = $state(''); let error = $state('');
let collapsed = $state(false); let collapsed = $state(false);
let curveCollapsed = $state(false);
let channel = $state<CurveChannel>('rgb'); let channel = $state<CurveChannel>('rgb');
const saved = $derived(readEditParams(photo)); const saved = $derived(readEditParams(photo));
@@ -401,7 +402,19 @@
<div class="border-border border-t pt-3"> <div class="border-border border-t pt-3">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<span class="text-text text-xs font-semibold">Кривая</span> <button
type="button"
class="text-text hover:text-primary flex items-center gap-1 text-xs font-semibold"
aria-expanded={!curveCollapsed}
aria-controls="light-curve"
onclick={() => (curveCollapsed = !curveCollapsed)}
>
<Icon
name="expand_more"
class="size-4 transition-transform {curveCollapsed ? '-rotate-90' : ''}"
/>
Кривая
</button>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
{#each CURVE_CHANNELS as ch (ch)} {#each CURVE_CHANNELS as ch (ch)}
<button <button
@@ -420,68 +433,72 @@
</div> </div>
</div> </div>
<svg {#if !curveCollapsed}
class="border-border mt-2 block aspect-square w-full touch-none rounded border" <div id="light-curve">
viewBox="0 0 100 100" <svg
role="application" class="border-border mt-2 block aspect-square w-full touch-none rounded border"
aria-label="Тоновая кривая" viewBox="0 0 100 100"
onpointerdown={onCurveDown} role="application"
onpointermove={onPointMove} aria-label="Тоновая кривая"
onpointerup={onPointUp} onpointerdown={onCurveDown}
onpointerleave={onPointUp} onpointermove={onPointMove}
> onpointerup={onPointUp}
<rect x="0" y="0" width="100" height="100" class="bg-bg" /> onpointerleave={onPointUp}
{#if histogram} >
{#each visibleChannels as ch (ch)} <rect x="0" y="0" width="100" height="100" class="bg-bg" />
{#if histogram}
{#each visibleChannels as ch (ch)}
<path
d={histogramPath(histogramValues(ch))}
fill="none"
stroke-width="0.4"
style="stroke: {CHANNEL_COLORS[ch]}; opacity: {ch === 'rgb' ? 0.45 : 0.7}"
/>
{/each}
{/if}
{#each [25, 50, 75] as step (step)}
<line x1={step} y1="0" x2={step} y2="100" class="stroke-border" stroke-width="0.4" />
<line x1="0" y1={step} x2="100" y2={step} class="stroke-border" stroke-width="0.4" />
{/each}
<path <path
d={histogramPath(histogramValues(ch))} d={curvePath(channel)}
fill="none" fill="none"
stroke-width="0.4" stroke-width="1.4"
style="stroke: {CHANNEL_COLORS[ch]}; opacity: {ch === 'rgb' ? 0.45 : 0.7}" style="stroke: {CHANNEL_COLORS[channel]}"
/> />
{/each} {#each channelPoints(channel) as point, index (index)}
{/if} <circle
{#each [25, 50, 75] as step (step)} cx={point.x * 100}
<line x1={step} y1="0" x2={step} y2="100" class="stroke-border" stroke-width="0.4" /> cy={(1 - point.y) * 100}
<line x1="0" y1={step} x2="100" y2={step} class="stroke-border" stroke-width="0.4" /> r="2.4"
{/each} class="cursor-grab"
<path stroke-width="1.2"
d={curvePath(channel)} style="fill: hsl(var(--bg)); stroke: {CHANNEL_COLORS[channel]}"
fill="none" role="slider"
stroke-width="1.4" tabindex="0"
style="stroke: {CHANNEL_COLORS[channel]}" aria-label="Точка кривой {index + 1}"
/> aria-valuemin="0"
{#each channelPoints(channel) as point, index (index)} aria-valuemax="1"
<circle aria-valuenow={point.x}
cx={point.x * 100} onpointerdown={(e) => onPointDown(e, index)}
cy={(1 - point.y) * 100} onkeydown={(e) => onPointKey(e, index)}
r="2.4" ></circle>
class="cursor-grab" {/each}
stroke-width="1.2" </svg>
style="fill: hsl(var(--bg)); stroke: {CHANNEL_COLORS[channel]}"
role="slider"
tabindex="0"
aria-label="Точка кривой {index + 1}"
aria-valuemin="0"
aria-valuemax="1"
aria-valuenow={point.x}
onpointerdown={(e) => onPointDown(e, index)}
onkeydown={(e) => onPointKey(e, index)}
></circle>
{/each}
</svg>
<div class="mt-2 flex items-center justify-between gap-2"> <div class="mt-2 flex items-center justify-between gap-2">
<span class="text-text-muted text-xs">Точечная кривая · линейная</span> <span class="text-text-muted text-xs">Точечная кривая · линейная</span>
<button <button
type="button" type="button"
class="border-border text-text-muted hover:text-text rounded-md border px-2 py-1 text-xs disabled:opacity-40" class="border-border text-text-muted hover:text-text rounded-md border px-2 py-1 text-xs disabled:opacity-40"
disabled={!canEdit || !channelPoints(channel).length} disabled={!canEdit || !channelPoints(channel).length}
onclick={resetCurve} onclick={resetCurve}
> >
Сбросить кривую Сбросить кривую
</button> </button>
</div> </div>
</div>
{/if}
</div> </div>
{#if busy} {#if busy}