35d8d57984
The colour band chips only aimed the mixer before; now they open the same panel the eyedropper opens, seeded with the colour the chip is named after (hexToRgb turns that readout back into the RGB bytes the picker stores). A pick already on the photo keeps its spot — only a mixer with nowhere to hang takes the middle of the frame. The HSL column itself is untouched: 8 chips, the rule, IMAGE, HUE/SAT/LUM, the readout and RESET all stay where they were.
90 lines
4.1 KiB
TypeScript
90 lines
4.1 KiB
TypeScript
import type { MsgKey } from '../i18n/vi';
|
|
import { useI18n } from '../i18n/I18nProvider';
|
|
|
|
// The app's nine tabs. LIGHT is the develop column and holds every knob of the
|
|
// picture — white balance, the tonal range, presence and the effects — so WB and
|
|
// FX, which used to be tabs of their own, are panels of it now. TOOLS is the one
|
|
// tab of features rather than records: the chips that write a value or take the
|
|
// pointer (AUTO, FIX, GRADIENT MASK, MONOCHROME), which used to share LIGHT's
|
|
// row with the two chips that are not knobs. The rail is a single column on
|
|
// desktop and becomes the bottom bar under 860px (see styles/app.css).
|
|
export type TabId = 'presets' | 'favorited' | 'photos' | 'light' | 'tools' | 'hsl' | 'frame' | 'save' | 'create';
|
|
|
|
// The build this bundle came from, put in by vite at build time and named by
|
|
// whoever built the image (see vite.config.ts). Empty under a dev server.
|
|
const VERSION = import.meta.env.VITE_VERSION ?? '';
|
|
|
|
export const TABS: { id: TabId; label: MsgKey; glyph: string }[] = [
|
|
{ id: 'presets', label: 'tab.presets', glyph: '◉' },
|
|
{ id: 'favorited', label: 'tab.favorited', glyph: '★' },
|
|
{ id: 'photos', label: 'tab.photos', glyph: '▤' },
|
|
{ id: 'light', label: 'tab.light', glyph: '☀' },
|
|
// The hammer and pick: the features that work ON the photo — AUTO's reading,
|
|
// FIX's brushes, GRADIENT MASK's shapes, the MONOCHROME switch.
|
|
{ id: 'tools', label: 'tab.tools', glyph: '⚒' },
|
|
// The HSL mixer sits with the colour tabs: it edits one hue band at a time.
|
|
{ id: 'hsl', label: 'tab.hsl', glyph: '◍' },
|
|
{ id: 'frame', label: 'tab.frame', glyph: '▣' },
|
|
// Keeping the look on the stage and building one from scratch are two acts,
|
|
// so they get two tabs: SAVE RECENT files what is open, CREATE RECIPES starts
|
|
// a recipe from its parts.
|
|
{ id: 'save', label: 'tab.save', glyph: '⤓' },
|
|
{ id: 'create', label: 'tab.create', glyph: '✎' },
|
|
];
|
|
|
|
export function ToolRail({
|
|
tab,
|
|
onTab,
|
|
onDropRecipe,
|
|
signedIn = false,
|
|
}: {
|
|
tab: TabId;
|
|
onTab: (tab: TabId) => void;
|
|
// A recipe chip dragged onto FAVORITED is filed there (see App's favorite()).
|
|
onDropRecipe?: (id: string) => void;
|
|
// MY PHOTOS is the account's own folder, so the tab is only offered once
|
|
// there is an account to hold one.
|
|
signedIn?: boolean;
|
|
}) {
|
|
const { t } = useI18n();
|
|
const favorited = (id: TabId) => id === 'favorited';
|
|
const tabs = signedIn ? TABS : TABS.filter((entry) => entry.id !== 'photos');
|
|
return (
|
|
<nav className="rail" aria-label="tool tabs">
|
|
{tabs.map((entry) => (
|
|
<button
|
|
key={entry.id}
|
|
type="button"
|
|
aria-pressed={tab === entry.id}
|
|
onClick={() => onTab(entry.id)}
|
|
// The star is a drop target: recipes are dragged onto it from the
|
|
// RECIPES strip rather than retyped.
|
|
onDragOver={(e) => {
|
|
if (!favorited(entry.id) || !onDropRecipe) return;
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'copy';
|
|
}}
|
|
onDrop={(e) => {
|
|
if (!favorited(entry.id) || !onDropRecipe) return;
|
|
e.preventDefault();
|
|
const id = e.dataTransfer.getData('text/plain');
|
|
if (id) onDropRecipe(id);
|
|
}}
|
|
>
|
|
<span className="glyph" aria-hidden="true">{entry.glyph}</span>
|
|
{/* A box, not a bare text node: on a phone the glyph is display:none
|
|
and the name would be the button's only child — an anonymous flex
|
|
item, which is the one thing iOS stops painting until a touch. */}
|
|
<span className="rail-label">{t(entry.label)}</span>
|
|
</button>
|
|
))}
|
|
{/* The build this page came off, at the foot of the rail: an image on the
|
|
NAS carries no other mark, so the version is how an operator tells the
|
|
one that is up from the one they just built. Stamped at build time
|
|
(vite.config), empty in a dev server, so the line only draws when
|
|
there is something to say. */}
|
|
{VERSION ? <span className="rail-version">{VERSION}</span> : null}
|
|
</nav>
|
|
);
|
|
}
|