import type { BookAlignEdge, BookAlignTo, BookSpread } from '@trek/shared'; import { distributableCount, isWholeGroup } from '@trek/shared'; import { AlignCenterHorizontal, AlignCenterVertical, AlignEndHorizontal, AlignEndVertical, AlignHorizontalDistributeCenter, AlignStartHorizontal, AlignStartVertical, AlignVerticalDistributeCenter, Group, Paintbrush, Pipette, Ungroup, type LucideIcon, } from 'lucide-react'; import { useState } from 'react'; import { useStudioStore } from '../../store/studioStore'; import { Tooltip } from '../shared/Tooltip'; import { Choice, Section } from './StudioControls'; import { useStudioShortcut } from './studioShortcut'; /** * Align, distribute, group and the style clipboard, for whatever is selected. * * The align bar lines a multi-selection up within its own box by default, and * a single element (or one whole group) against its page, since there is * nothing else for it to line up with. Groups move as one box throughout * (shared/src/book/book-arrange.ts). Every press is one undo step. */ const ALIGN: { edge: BookAlignEdge; icon: LucideIcon; labelKey: string }[] = [ { edge: 'left', icon: AlignStartVertical, labelKey: 'journey.studio.layers.alignLeft' }, { edge: 'center', icon: AlignCenterVertical, labelKey: 'journey.studio.layers.alignCenter' }, { edge: 'right', icon: AlignEndVertical, labelKey: 'journey.studio.layers.alignRight' }, { edge: 'top', icon: AlignStartHorizontal, labelKey: 'journey.studio.layers.alignTop' }, { edge: 'middle', icon: AlignCenterHorizontal, labelKey: 'journey.studio.layers.alignMiddle' }, { edge: 'bottom', icon: AlignEndHorizontal, labelKey: 'journey.studio.layers.alignBottom' }, ]; /** How many things move as one in a selection: a group counts once. */ function unitCount(spread: BookSpread, ids: string[]): number { return distributableCount( spread, spread.elements.filter((e) => ids.includes(e.id)) ); } function IconButton({ label, icon: Icon, onClick, disabled, shortcut, }: { label: string; icon: LucideIcon; onClick: () => void; disabled?: boolean; shortcut?: readonly string[]; }) { return ( ); } export function StudioArrangeSection({ spreadIndex, t }: { spreadIndex: number; t: (k: string) => string }) { const spread = useStudioStore((s) => s.doc?.spreads[spreadIndex]); const selection = useStudioStore((s) => s.selection); const hasStyle = useStudioStore((s) => s.styleClip !== null); const align = useStudioStore((s) => s.align); const distribute = useStudioStore((s) => s.distribute); const group = useStudioStore((s) => s.group); const ungroup = useStudioStore((s) => s.ungroup); const copyStyle = useStudioStore((s) => s.copyStyle); const pasteStyle = useStudioStore((s) => s.pasteStyle); const keys = useStudioShortcut(); const [chosenTo, setChosenTo] = useState(null); if (!spread || !selection.length) return null; const sel = spread.elements.filter((e) => selection.includes(e.id)); const movable = sel.filter((e) => !e.locked).map((e) => e.id); const units = unitCount(spread, movable); // One thing has nothing to line up with but its page. const to: BookAlignTo = units < 2 ? (chosenTo === 'safe' ? 'safe' : 'page') : (chosenTo ?? 'selection'); const grouped = sel.some((e) => e.group); const canGroup = movable.length >= 2 && !isWholeGroup(spread, movable); const asGroup = selection.length > 1 && isWholeGroup(spread, selection); return ( // Open for a multi-selection, where lining up is what comes next; folded for one element.
1 ? 'many' : 'one'} label={t('journey.studio.layers.align')} defaultOpen={units > 1}>
{ALIGN.map((a) => ( align(spreadIndex, a.edge, to)} /> ))} distribute(spreadIndex, 'horizontal')} /> distribute(spreadIndex, 'vertical')} />
{t('journey.studio.layers.alignTo')} value={to} ariaLabel={t('journey.studio.layers.alignTo')} options={[ { value: 'selection' as const, label: t('journey.studio.layers.toSelection'), disabled: units < 2 }, { value: 'page' as const, label: t('journey.studio.layers.toPage') }, { value: 'safe' as const, label: t('journey.studio.layers.toSafe') }, ]} onPick={setChosenTo} />
{(canGroup || !grouped) && ( group(spreadIndex)} /> )} {grouped && ( ungroup(spreadIndex)} /> )} copyStyle(spreadIndex)} /> pasteStyle(spreadIndex)} />
{asGroup &&

{t('journey.studio.layers.groupHint')}

}
); }