feat(studio): add right-click context menu (Solo mode, Collapse all, Expand all) for DevelopPanels, pin Color Chrome sub-chips, and fix library single view sorting & time range select
This commit is contained in:
+59
-14
@@ -747,7 +747,8 @@ export function Workspace() {
|
||||
|
||||
// The photo half of the session, back out of IndexedDB. It waits for the
|
||||
// account: which file may open depends on the tier, and the check reads it
|
||||
// from the render — run it too early and a PRO's own RAW is turned away.
|
||||
const openLibraryPhotoRef = useRef<((id: string) => Promise<void>) | null>(null);
|
||||
|
||||
// When the stored session is empty but a RAW is still parked in OPFS, the RAW
|
||||
// is what "the last photo" means: develop it again rather than open on
|
||||
// nothing. A frame handed over by the catalogue (`/app?lib=<id>`) wins over
|
||||
@@ -756,19 +757,22 @@ export function Workspace() {
|
||||
useEffect(() => {
|
||||
if (!authReady) return;
|
||||
let alive = true;
|
||||
const wanted = new URLSearchParams(window.location.search).get('lib');
|
||||
if (wanted) openLibraryPhoto(wanted);
|
||||
else
|
||||
|
||||
const checkAndLoad = () => {
|
||||
const wanted = new URLSearchParams(window.location.search).get('lib');
|
||||
if (wanted && openLibraryPhotoRef.current) {
|
||||
void openLibraryPhotoRef.current(wanted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!checkAndLoad()) {
|
||||
loadPhoto()
|
||||
.then(async (photo) => {
|
||||
if (!alive) return;
|
||||
const show = (row: { name: string; bytes: Uint8Array }) =>
|
||||
adoptPhoto(row.bytes, row.name, new Blob([row.bytes as BlobPart], { type: 'image/jpeg' }), true);
|
||||
// A session photo named like a RAW is a develop an older build left
|
||||
// there — the RAW branch writes none any more — and it carries that
|
||||
// build's colour, which is how a fixed develop kept opening olive. The
|
||||
// parked RAW is the copy that is not stale, so it is read first; the
|
||||
// session's is only shown when nothing is parked to develop.
|
||||
if (photo && !isRawName(photo.name)) return show(photo);
|
||||
const parked = await readRaw();
|
||||
if (!alive) return;
|
||||
@@ -777,10 +781,17 @@ export function Workspace() {
|
||||
await adoptPhoto(jpeg, parked.name, new Blob([jpeg as BlobPart], { type: 'image/jpeg' }), true, parked.bytes);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
const onPop = () => {
|
||||
checkAndLoad();
|
||||
};
|
||||
window.addEventListener('popstate', onPop);
|
||||
|
||||
return () => {
|
||||
alive = false;
|
||||
window.removeEventListener('popstate', onPop);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [authReady]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -2091,6 +2102,10 @@ export function Workspace() {
|
||||
[loadFile, applyRecipe, clearPreview, t]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
openLibraryPhotoRef.current = openLibraryPhoto;
|
||||
}, [openLibraryPhoto]);
|
||||
|
||||
// What the catalogue remembers about a frame is the look it was left at, filed
|
||||
// back on every change — debounced, because a slider drag fires this per frame
|
||||
// of the drag and IndexedDB is on the wrong side of that.
|
||||
@@ -3359,10 +3374,40 @@ export function Workspace() {
|
||||
}))}
|
||||
/>
|
||||
),
|
||||
// The two Color Chromes close WB: same switch, the other axis of the two
|
||||
// tracks above it, so they are read under the pair they are read against —
|
||||
// and on the tab whose colour they are.
|
||||
wbFoot: <ChipRow grid chips={chromeChips} />,
|
||||
wbFoot: (
|
||||
<div className="dev-box" data-key="dev-box-color-chrome" style={{ display: 'flex', flexDirection: 'column', gap: '8px', padding: '8px 0' }}>
|
||||
<div>
|
||||
<span className="dev-box-title" style={{ marginBottom: '4px', display: 'block', fontSize: '11px', opacity: 0.8 }}>COLOR CHROME</span>
|
||||
<ChipRow
|
||||
grid
|
||||
chips={CHROME_OPTIONS.map((opt) => ({
|
||||
key: `cx-${opt.v}`,
|
||||
label: opt.d,
|
||||
active: (recipe.adjustments.colorChrome || 'none') === opt.v,
|
||||
onClick: () => {
|
||||
remember();
|
||||
setAdjustment({ colorChrome: opt.v as Recipe['adjustments']['colorChrome'] });
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="dev-box-title" style={{ marginBottom: '4px', display: 'block', fontSize: '11px', opacity: 0.8 }}>CHROME BLUE</span>
|
||||
<ChipRow
|
||||
grid
|
||||
chips={CHROME_OPTIONS.map((opt) => ({
|
||||
key: `cxb-${opt.v}`,
|
||||
label: opt.d,
|
||||
active: (recipe.adjustments.colorChromeBlue || 'none') === opt.v,
|
||||
onClick: () => {
|
||||
remember();
|
||||
setAdjustment({ colorChromeBlue: opt.v as Recipe['adjustments']['colorChromeBlue'] });
|
||||
},
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
// TONE CURVE closes TONE: the graph on the photo is the shape of the range
|
||||
// the panel's seven rows are, so it stands under them rather than in a row
|
||||
// of its own at the top of the tab — and it glows amber once the graph is
|
||||
|
||||
Reference in New Issue
Block a user