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 error = $state('');
let collapsed = $state(false);
let curveCollapsed = $state(false);
let channel = $state<CurveChannel>('rgb');
const saved = $derived(readEditParams(photo));
@@ -401,7 +402,19 @@
<div class="border-border border-t pt-3">
<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">
{#each CURVE_CHANNELS as ch (ch)}
<button
@@ -420,68 +433,72 @@
</div>
</div>
<svg
class="border-border mt-2 block aspect-square w-full touch-none rounded border"
viewBox="0 0 100 100"
role="application"
aria-label="Тоновая кривая"
onpointerdown={onCurveDown}
onpointermove={onPointMove}
onpointerup={onPointUp}
onpointerleave={onPointUp}
>
<rect x="0" y="0" width="100" height="100" class="bg-bg" />
{#if histogram}
{#each visibleChannels as ch (ch)}
{#if !curveCollapsed}
<div id="light-curve">
<svg
class="border-border mt-2 block aspect-square w-full touch-none rounded border"
viewBox="0 0 100 100"
role="application"
aria-label="Тоновая кривая"
onpointerdown={onCurveDown}
onpointermove={onPointMove}
onpointerup={onPointUp}
onpointerleave={onPointUp}
>
<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
d={histogramPath(histogramValues(ch))}
d={curvePath(channel)}
fill="none"
stroke-width="0.4"
style="stroke: {CHANNEL_COLORS[ch]}; opacity: {ch === 'rgb' ? 0.45 : 0.7}"
stroke-width="1.4"
style="stroke: {CHANNEL_COLORS[channel]}"
/>
{/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
d={curvePath(channel)}
fill="none"
stroke-width="1.4"
style="stroke: {CHANNEL_COLORS[channel]}"
/>
{#each channelPoints(channel) as point, index (index)}
<circle
cx={point.x * 100}
cy={(1 - point.y) * 100}
r="2.4"
class="cursor-grab"
stroke-width="1.2"
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>
{#each channelPoints(channel) as point, index (index)}
<circle
cx={point.x * 100}
cy={(1 - point.y) * 100}
r="2.4"
class="cursor-grab"
stroke-width="1.2"
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">
<span class="text-text-muted text-xs">Точечная кривая · линейная</span>
<button
type="button"
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}
onclick={resetCurve}
>
Сбросить кривую
</button>
</div>
<div class="mt-2 flex items-center justify-between gap-2">
<span class="text-text-muted text-xs">Точечная кривая · линейная</span>
<button
type="button"
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}
onclick={resetCurve}
>
Сбросить кривую
</button>
</div>
</div>
{/if}
</div>
{#if busy}