feat(studio): MY PHOTOS, FAVORITED, PHOTO STYLE and RECIPES read as a list of names, and their column is the visitor's
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TABS, ToolRail, type TabId } from './ui/ToolRail';
|
||||
import { ChipRow, MiniSlider, SliderRow, type ChipDef, type SliderDef } from './ui/ChipColumn';
|
||||
import { ChipRow, ColGrip, MiniSlider, SliderRow, type ChipDef, type SliderDef } from './ui/ChipColumn';
|
||||
import { ImageStage } from './ui/ImageStage';
|
||||
import { StraightenRuler } from './ui/StraightenRuler';
|
||||
import { ToneCurvePanel } from './ui/ToneCurvePanel';
|
||||
@@ -3291,6 +3291,11 @@ export function Workspace() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
})();
|
||||
|
||||
// The two tabs whose own chips are names to read down rather than buttons to
|
||||
// press out of a row: their column drops the chip's chrome (ChipRow's `list`)
|
||||
// and takes a grip.
|
||||
const listTab = tab === 'photos' || tab === 'favorited';
|
||||
|
||||
// FIX's two brushes: a chip arms one, the wheel sizes it — the chip's own
|
||||
// readout is that size, since the brush has no size buttons to point at — and
|
||||
// CLEAR hands the whole photo back. HEAL repairs a speck of dust, MOSAIC hides
|
||||
@@ -3829,10 +3834,13 @@ export function Workspace() {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ChipRow chips={chips} />
|
||||
<ChipRow chips={chips} list={listTab} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* MY PHOTOS and FAVORITED are lists of names rather than buttons, so
|
||||
their column is the visitor's to widen (see ColGrip). */}
|
||||
{listTab ? <ColGrip col="main" /> : null}
|
||||
{tab === 'favorited' && saved.length === 0 ? <p className="hint">{t('sec.savedEmpty')}</p> : null}
|
||||
{/* The mixer's readout: the colour the eyedropper last read, as the
|
||||
RGB bytes and the HSL the band is indexed by. */}
|
||||
@@ -3952,7 +3960,10 @@ export function Workspace() {
|
||||
{/* column 3 — the open chip's sub-chips, or PRESETS' RECIPES list */}
|
||||
{optGroup ? (
|
||||
<div className="col col-sub strip" data-col="options">
|
||||
<ChipRow chips={stripChips(optGroup)} />
|
||||
<ChipRow chips={stripChips(optGroup)} list={optGroup === 'style'} />
|
||||
{/* PHOTO STYLE is one name per look, so its strip is the visitor's
|
||||
to widen — the sims' names are longer than the column. */}
|
||||
{optGroup === 'style' ? <ColGrip col="options" /> : null}
|
||||
{/* A watermark's colour: the presets above are a shortcut, the
|
||||
well under them picks any colour at all. */}
|
||||
{optGroup === 'wmColor' || optGroup === 'gpsColor' ? (
|
||||
@@ -3995,7 +4006,10 @@ export function Workspace() {
|
||||
{tab === 'presets' && showRecipes ? (
|
||||
<div className="col col-sub strip" data-col="recipes">
|
||||
{recipeStrip.length === 0 ? <p className="hint">{t('sec.savedEmpty')}</p> : null}
|
||||
<ChipRow chips={recipeStrip} />
|
||||
<ChipRow chips={recipeStrip} list />
|
||||
{/* RECIPES reads the same way MY PHOTOS does: names, and a width
|
||||
of the visitor's own. */}
|
||||
<ColGrip col="recipes" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -105,7 +105,33 @@
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid var(--border-soft);
|
||||
/* The grip on this column's edge is placed against it (see .col-grip). */
|
||||
position: relative;
|
||||
}
|
||||
/* A list read as names instead of as buttons: MY PHOTOS' saved frames, FAVORITED
|
||||
and RECIPES' looks, PHOTO STYLE's sims. No border, no fill, no pill — the text
|
||||
alone, with the accent still naming the one in force. */
|
||||
.chip-row.list .chip {
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.chip-row.list .chip:hover .chip-label { text-decoration: underline; }
|
||||
/* The edge of a column, dragged to widen it (see ColGrip): a strip of the cursor
|
||||
over the border that is already there. `touch-action: none` is what lets a
|
||||
finger drag it rather than scroll the column. */
|
||||
.col-grip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -4px;
|
||||
bottom: 0;
|
||||
width: 9px;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
z-index: 3;
|
||||
}
|
||||
.col-grip:hover, .col-grip:active { background: var(--accent-soft); }
|
||||
.col:last-child { border-right: none; }
|
||||
.col-slider { width: var(--slider-w); }
|
||||
/* CREATE RECIPES draws a two-up form, so its column is the wide one. */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { keep, recall } from '../remember';
|
||||
|
||||
// One strip at a time — the same shape as the phone's AdjustmentPanel: a ruler
|
||||
// row for the open slider, an options strip for the open group, then the tab's
|
||||
@@ -62,10 +63,13 @@ export interface SliderDef {
|
||||
|
||||
// `grid` lays the row out as the table a set of same-kind values is read as
|
||||
// (WB's presets, LIGHT's two Color Chromes) instead of the wrapping strip the
|
||||
// option rows use.
|
||||
export function ChipRow({ chips, grid, grid3 }: { chips: ChipDef[]; grid?: boolean; grid3?: boolean }) {
|
||||
// option rows use. `list` is the other end of it: the rows that are a LIST OF
|
||||
// NAMES — MY PHOTOS' frames, FAVORITED and RECIPES' looks, PHOTO STYLE's sims —
|
||||
// drop the button's chrome and read as plain text, with only the accent marking
|
||||
// the one in force (see .chip-row.list).
|
||||
export function ChipRow({ chips, grid, grid3, list }: { chips: ChipDef[]; grid?: boolean; grid3?: boolean; list?: boolean }) {
|
||||
return (
|
||||
<div className={`chip-row${grid3 ? ' grid-3' : grid ? ' grid' : ''}`}>
|
||||
<div className={`chip-row${list ? ' list' : ''}${grid3 ? ' grid-3' : grid ? ' grid' : ''}`}>
|
||||
{chips.map((chip) =>
|
||||
chip.divider ? (
|
||||
<span key={chip.key} className="chip-rule" role="separator" aria-orientation="horizontal" />
|
||||
@@ -114,6 +118,48 @@ export function ChipRow({ chips, grid, grid3 }: { chips: ChipDef[]; grid?: boole
|
||||
);
|
||||
}
|
||||
|
||||
// The edge of a column you can drag: the width is written on the column itself
|
||||
// and remembered (remember.ts), so the next visit opens at the size it was left
|
||||
// at. Drag, not a panel: the grip appears where the column's edge already is.
|
||||
// ponytail: the DOM takes the width and React never learns it — a column width
|
||||
// is presentation, not state, and a re-render carrying it would fight the drag.
|
||||
export function ColGrip({ col, min = 110, max = 560 }: { col: string; min?: number; max?: number }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const key = `studio-col-${col}`;
|
||||
useEffect(() => {
|
||||
const box = ref.current?.parentElement;
|
||||
const kept = recall(key);
|
||||
if (box && kept) box.style.width = kept;
|
||||
}, [key]);
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="col-grip"
|
||||
data-key={`col-grip-${col}`}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label={`resize ${col}`}
|
||||
onPointerDown={(e) => {
|
||||
const box = ref.current?.parentElement;
|
||||
if (!box) return;
|
||||
e.preventDefault();
|
||||
const from = e.clientX;
|
||||
const start = box.getBoundingClientRect().width;
|
||||
const move = (m: PointerEvent) => {
|
||||
box.style.width = `${Math.round(Math.min(max, Math.max(min, start + m.clientX - from)))}px`;
|
||||
};
|
||||
const up = () => {
|
||||
window.removeEventListener('pointermove', move);
|
||||
window.removeEventListener('pointerup', up);
|
||||
keep(key, box.style.width);
|
||||
};
|
||||
window.addEventListener('pointermove', move);
|
||||
window.addEventListener('pointerup', up);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SliderRow({ slider }: { slider: SliderDef }) {
|
||||
// The wheel belongs to the open ruler: one notch is one step, held inside the
|
||||
// parameter's own range. The listener is native because React's is passive —
|
||||
|
||||
Reference in New Issue
Block a user