feat(library & studio): add EXIF info bar, portrait frame panning, and multi-photo selection with Panorama/HDR context menu

This commit is contained in:
2026-10-07 10:44:59 +07:00
parent 7b836984fc
commit 3b4c49c41d
9 changed files with 196 additions and 49 deletions
+19 -9
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { useI18n } from '../i18n/I18nProvider';
import { MIN_CROP_FRAC, type CropRect, type GradientMask, type HealSpot, type MosaicSpot } from '../../shared/types';
import { bitmapReader, samplePixel } from '../engine/imageOps';
import { MIN_CROP_FRAC, type CropRect, type FrameId, type GradientMask, type HealSpot, type MosaicSpot } from '../../shared/types';
import { bitmapReader, samplePixel, specsLine, type ShotSpecs } from '../engine/imageOps';
import { RAW_ACCEPT } from '../engine/rawDevelop';
// A phone's HEIC is offered beside the camera RAWs: it is the same offer to a
// file the browser will not open by itself (see engine/heicDevelop.ts).
@@ -286,6 +286,11 @@ export function ImageStage({
onMaskCreate,
onMaskEdit,
dims = null,
shotSpecs = null,
place = null,
frameId = 'none',
framePanX = 0,
onFramePanXChange,
}: {
previewUrl: string | null;
originalUrl: string | null;
@@ -377,6 +382,11 @@ export function ImageStage({
// the quarter turn and cut by a committed crop, so the number in the row under
// the photo is the resolution an export at the photo's own size renders.
dims?: { w: number; h: number } | null;
shotSpecs?: ShotSpecs | null;
place?: string | null;
frameId?: FrameId;
framePanX?: number;
onFramePanXChange?: (panX: number) => void;
}) {
const showStraighten = !!straightenRuler;
// The curve graph is a fresh node on every parent render too, and the measure
@@ -1987,14 +1997,13 @@ export function ImageStage({
)}
{previewUrl ? (
<div className="toolbar">
{/* The row's own facts, left of its buttons: how big the photo under
the stage is. The number is the render's, so a crop moves it. */}
{dims ? (
<span className="hint dims" data-key="photo-dims">
{dims.w} × {dims.h}
</span>
<>
{shotSpecs || place || dims ? (
<div className="photo-meta-bar" data-key="photo-meta-bar">
{specsLine(shotSpecs ?? null, place, dims)}
</div>
) : null}
<div className="toolbar">
{onClear ? (
<button type="button" className="btn" data-key="clear-photo" onClick={onClear}>
{t('drop.clear')}
@@ -2044,6 +2053,7 @@ export function ImageStage({
</button>
<span className="hint">{t('drop.peekHint')}</span>
</div>
</>
) : null}
{busy ? <div className="busy">{busy}</div> : null}