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:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ColorAdjustments } from '../../shared/types';
|
||||
import { PARAM_DEFS } from '../../shared/utils/paramDefs';
|
||||
@@ -98,6 +98,7 @@ function DevRow({
|
||||
// slugs, comma-joined; a slug no panel answers to is dropped on the way in, so a
|
||||
// panel that is renamed or taken out does not leave a hole in the column.
|
||||
const OPEN_KEY = 'recipescam.develop.open';
|
||||
const SOLO_KEY = 'recipescam.develop.soloMode';
|
||||
|
||||
// The studio's right column: every panel's knobs behind a disclosure that folds
|
||||
// its own rows away, and the column opens with all of them folded — a dozen rows
|
||||
@@ -117,15 +118,67 @@ export function DevelopPanels({
|
||||
onPro?: () => void;
|
||||
slots?: { wb?: ReactNode; wbFoot?: ReactNode; toneFoot?: ReactNode; effects?: ReactNode };
|
||||
}) {
|
||||
const [soloMode, setSoloMode] = useState<boolean>(() => recall(SOLO_KEY) === 'true');
|
||||
const [open, setOpen] = useState<Record<string, boolean>>(() => {
|
||||
const saved = new Set((recall(OPEN_KEY) ?? '').split(','));
|
||||
return Object.fromEntries(PANELS.filter((panel) => saved.has(panel.slug)).map((panel) => [panel.slug, true]));
|
||||
});
|
||||
const fold = (slug: string, isOpen: boolean) => {
|
||||
const next = { ...open, [slug]: !isOpen };
|
||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextMenu) return;
|
||||
const close = () => setContextMenu(null);
|
||||
window.addEventListener('click', close);
|
||||
window.addEventListener('keydown', (e) => e.key === 'Escape' && close());
|
||||
return () => {
|
||||
window.removeEventListener('click', close);
|
||||
};
|
||||
}, [contextMenu]);
|
||||
|
||||
const saveOpen = (next: Record<string, boolean>) => {
|
||||
setOpen(next);
|
||||
keep(OPEN_KEY, PANELS.filter((panel) => next[panel.slug]).map((panel) => panel.slug).join(','));
|
||||
};
|
||||
|
||||
const fold = (slug: string, isOpen: boolean) => {
|
||||
if (soloMode) {
|
||||
if (isOpen) {
|
||||
saveOpen({ ...open, [slug]: false });
|
||||
} else {
|
||||
const next = Object.fromEntries(PANELS.map((p) => [p.slug, p.slug === slug]));
|
||||
saveOpen(next);
|
||||
}
|
||||
} else {
|
||||
saveOpen({ ...open, [slug]: !isOpen });
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSolo = () => {
|
||||
const nextSolo = !soloMode;
|
||||
setSoloMode(nextSolo);
|
||||
keep(SOLO_KEY, String(nextSolo));
|
||||
if (nextSolo) {
|
||||
const activeSlug = PANELS.find((p) => open[p.slug])?.slug ?? 'wb';
|
||||
const next = Object.fromEntries(PANELS.map((p) => [p.slug, p.slug === activeSlug]));
|
||||
saveOpen(next);
|
||||
}
|
||||
setContextMenu(null);
|
||||
};
|
||||
|
||||
const collapseAll = () => {
|
||||
saveOpen(Object.fromEntries(PANELS.map((p) => [p.slug, false])));
|
||||
setContextMenu(null);
|
||||
};
|
||||
|
||||
const expandAll = () => {
|
||||
if (soloMode) {
|
||||
setSoloMode(false);
|
||||
keep(SOLO_KEY, 'false');
|
||||
}
|
||||
saveOpen(Object.fromEntries(PANELS.map((p) => [p.slug, true])));
|
||||
setContextMenu(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dev-panels">
|
||||
{PANELS.map((panel) => {
|
||||
@@ -139,6 +192,10 @@ export function DevelopPanels({
|
||||
className="dev-panel-head"
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => fold(panel.slug, isOpen)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setContextMenu({ x: Math.min(e.clientX, window.innerWidth - 180), y: Math.min(e.clientY, window.innerHeight - 150) });
|
||||
}}
|
||||
>
|
||||
<span className="dev-panel-label">{panel.title}</span>
|
||||
<span className="dev-caret" aria-hidden="true">{isOpen ? '▾' : '▸'}</span>
|
||||
@@ -156,6 +213,87 @@ export function DevelopPanels({
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
|
||||
{contextMenu ? (
|
||||
<div
|
||||
className="adm-context-menu"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: contextMenu.y,
|
||||
left: contextMenu.x,
|
||||
zIndex: 99999,
|
||||
background: 'var(--color-bg-elevated, #232328)',
|
||||
border: '1px solid var(--color-border, #3a3a42)',
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0 8px 24px rgba(0,0,0,0.6)',
|
||||
padding: '4px 0',
|
||||
minWidth: '150px',
|
||||
fontSize: '12px',
|
||||
color: '#eee',
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
width: '100%',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
color: 'currentColor',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
onClick={toggleSolo}
|
||||
>
|
||||
<input type="checkbox" checked={soloMode} readOnly style={{ pointerEvents: 'none', cursor: 'pointer' }} />
|
||||
<span>Solo Mode</span>
|
||||
</button>
|
||||
<div style={{ height: '1px', background: 'var(--color-border, #3a3a42)', margin: '4px 0' }} />
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
width: '100%',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
color: 'currentColor',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
onClick={collapseAll}
|
||||
>
|
||||
<span>Collapse All</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
width: '100%',
|
||||
padding: '6px 12px',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
color: 'currentColor',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
onClick={expandAll}
|
||||
>
|
||||
<span>Expand All</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user