feat(library & studio): add EXIF info bar, portrait frame panning, and multi-photo selection with Panorama/HDR context menu
This commit is contained in:
@@ -15,7 +15,7 @@ import { RecipeCreatePanel, type RecipeDraft } from './ui/RecipeCreatePanel';
|
||||
import { useI18n } from './i18n/I18nProvider';
|
||||
import { loadEngine, MARK_FACES } from './engine/assets';
|
||||
import { renderPhoto, type RenderAssets } from './engine/exportEngine';
|
||||
import { download, fileToBytes, readCapturedAt, readGps, readIso, resizedJpeg, scaledJpeg } from './engine/imageOps';
|
||||
import { download, fileToBytes, readCapturedAt, readGps, readIso, readSpecs, resizedJpeg, scaledJpeg, type ShotSpecs } from './engine/imageOps';
|
||||
import { developRaw, isRawName } from './engine/rawDevelop';
|
||||
import { heicToJpeg, isHeicName } from './engine/heicDevelop';
|
||||
import { clearRaw, readRaw, storeRaw } from './engine/rawStore';
|
||||
@@ -383,6 +383,7 @@ export function Workspace() {
|
||||
// UNDO and every knob still speak about the look the click left behind.
|
||||
const [preview, setPreview] = useState<Recipe | null>(null);
|
||||
const [frameId, setFrameId] = useState<FrameId>(() => session.frameId ?? 'none');
|
||||
const [framePanX, setFramePanX] = useState<number>(() => session.framePanX ?? 0);
|
||||
const [crop, setCrop] = useState<CropRatio>(() => session.crop ?? 'none');
|
||||
const [cropRect, setCropRect] = useState<CropRect>(() => session.cropRect ?? DEFAULT_CROP_RECT);
|
||||
const [cropApplied, setCropApplied] = useState(() => session.cropApplied ?? false);
|
||||
@@ -440,6 +441,7 @@ export function Workspace() {
|
||||
const [gps, setGps] = useState<GPSInfo | null>(() => session.gps ?? null);
|
||||
// ISO off the loaded photo's EXIF; null means the strip prints AUTO.
|
||||
const [iso, setIso] = useState<number | null>(null);
|
||||
const [shotSpecs, setShotSpecs] = useState<ShotSpecs | null>(null);
|
||||
// When the loaded photo was taken, off its own EXIF. The GPS stamp prints this
|
||||
// and not the clock, whichever way the position was come by: a photo from the
|
||||
// library can be years old, and the location typed in for it today is still the
|
||||
@@ -936,6 +938,7 @@ export function Workspace() {
|
||||
setGpsManual({ lat: '', lng: '' });
|
||||
setUseGeotag(false);
|
||||
setIso(null);
|
||||
setShotSpecs(null);
|
||||
setError(null);
|
||||
setBusy(null);
|
||||
void forgetPhoto();
|
||||
@@ -1024,6 +1027,7 @@ export function Workspace() {
|
||||
const tagged = exifBytes ?? bytes;
|
||||
const found = await readGps(tagged);
|
||||
const foundIso = await readIso(tagged);
|
||||
const foundSpecs = await readSpecs(tagged);
|
||||
// The photo's own date, held apart from its GPS: a frame with a date but no
|
||||
// position still stamps the date when the position is typed in or asked of
|
||||
// the device (see the two places the clock is fallen back on, below).
|
||||
@@ -1053,6 +1057,7 @@ export function Workspace() {
|
||||
setPreviewBytes(preview);
|
||||
setGps(found);
|
||||
setIso(foundIso);
|
||||
setShotSpecs(foundSpecs);
|
||||
const def = defaultLook();
|
||||
restoreLook(def);
|
||||
setUndo([def]);
|
||||
@@ -3903,6 +3908,14 @@ export function Workspace() {
|
||||
cropRect={cropRect}
|
||||
cropWH={cropWH}
|
||||
dims={stageDims}
|
||||
frameId={cropEditing ? 'none' : frameId}
|
||||
framePanX={framePanX}
|
||||
onFramePanXChange={(pan) => {
|
||||
setFramePanX(pan);
|
||||
setRecipe((prev) => ({ ...prev, framePanX: pan }));
|
||||
}}
|
||||
shotSpecs={shotSpecs}
|
||||
place={place}
|
||||
onCropRectChange={setCropRect}
|
||||
onStageZoom={onStageZoom}
|
||||
canCompare={!!source}
|
||||
|
||||
Reference in New Issue
Block a user