fix(library): fix Olympus ORF raw color conversion and add custom photo context menu modal
This commit is contained in:
@@ -228,6 +228,7 @@ export function Library() {
|
||||
// one more folder; `root` means it landed on a row at the top level — the head
|
||||
// of a tree — which also carries folding the whole tree.
|
||||
const [menu, setMenu] = useState<{ folder: LibraryFolder | null; root?: boolean; rel?: string; node?: string; x: number; y: number } | null>(null);
|
||||
const [photoMenu, setPhotoMenu] = useState<{ photo: LibraryPhoto; x: number; y: number } | null>(null);
|
||||
// A reading of this roll that is still waiting its turn. The menu is where
|
||||
// that wait is called off, so it has to know one exists.
|
||||
const queued = menu?.folder ? scanQueue().some((j) => j.folder === menu.folder!.name) : false;
|
||||
@@ -574,13 +575,17 @@ export function Library() {
|
||||
// behind a full-screen scrim: a scrim would eat the click that opens the next
|
||||
// folder, which is half of how this screen is used.
|
||||
useEffect(() => {
|
||||
if (!menu) return;
|
||||
if (!menu && !photoMenu) return;
|
||||
const away = (e: Event) => {
|
||||
if (e.target instanceof Element && e.target.closest('.lib-menu')) return;
|
||||
setMenu(null);
|
||||
setPhotoMenu(null);
|
||||
};
|
||||
const key = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setMenu(null);
|
||||
if (e.key === 'Escape') {
|
||||
setMenu(null);
|
||||
setPhotoMenu(null);
|
||||
}
|
||||
};
|
||||
document.addEventListener('pointerdown', away);
|
||||
document.addEventListener('keydown', key);
|
||||
@@ -588,7 +593,7 @@ export function Library() {
|
||||
document.removeEventListener('pointerdown', away);
|
||||
document.removeEventListener('keydown', key);
|
||||
};
|
||||
}, [menu]);
|
||||
}, [menu, photoMenu]);
|
||||
|
||||
// A reading asked for here is a request, not a reading: one walk runs at a time,
|
||||
// and a folder asked for while another is being read waits its turn — which is
|
||||
@@ -1517,7 +1522,19 @@ export function Library() {
|
||||
<div className="adm-grid" ref={wall.ref}>
|
||||
{wall.above > 0 ? <span className="adm-wall-gap" style={{ height: wall.above }} aria-hidden="true" /> : null}
|
||||
{painted.map((photo) => (
|
||||
<article className="adm-card" key={photo.id} data-key={`lib-photo-${photo.id}`}>
|
||||
<article
|
||||
className="adm-card"
|
||||
key={photo.id}
|
||||
data-key={`lib-photo-${photo.id}`}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setPhotoMenu({
|
||||
photo,
|
||||
x: Math.min(e.clientX, window.innerWidth - 220),
|
||||
y: Math.min(e.clientY, window.innerHeight - 160),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{tile(photo, () => void open(photo))}
|
||||
<div className="adm-meta">
|
||||
<b>{photo.name}</b>
|
||||
@@ -1545,11 +1562,31 @@ export function Library() {
|
||||
src={stageUrl || urls[frame.id]!}
|
||||
alt=""
|
||||
onClick={() => void open(frame)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setPhotoMenu({
|
||||
photo: frame,
|
||||
x: Math.min(e.clientX, window.innerWidth - 220),
|
||||
y: Math.min(e.clientY, window.innerHeight - 160),
|
||||
});
|
||||
}}
|
||||
title={t('lib.open')}
|
||||
style={zoom === 1 ? undefined : { transformOrigin: zoomAt, transform: `scale(${zoom})` }}
|
||||
/>
|
||||
) : (
|
||||
<div className="lib-noimg" onClick={() => void open(frame)} title={t('lib.open')}>
|
||||
<div
|
||||
className="lib-noimg"
|
||||
onClick={() => void open(frame)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setPhotoMenu({
|
||||
photo: frame,
|
||||
x: Math.min(e.clientX, window.innerWidth - 220),
|
||||
y: Math.min(e.clientY, window.innerHeight - 160),
|
||||
});
|
||||
}}
|
||||
title={t('lib.open')}
|
||||
>
|
||||
RAW
|
||||
</div>
|
||||
)}
|
||||
@@ -1626,6 +1663,14 @@ export function Library() {
|
||||
data-key={`lib-thumb-${photo.id}`}
|
||||
title={photo.name}
|
||||
onClick={() => setUp(photo.id)}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
setPhotoMenu({
|
||||
photo,
|
||||
x: Math.min(e.clientX, window.innerWidth - 220),
|
||||
y: Math.min(e.clientY, window.innerHeight - 160),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{tile(photo)}
|
||||
<span className="adm-thumb-cap">{photo.name}</span>
|
||||
@@ -1797,6 +1842,74 @@ export function Library() {
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{photoMenu ? (
|
||||
<div
|
||||
className="lib-menu"
|
||||
data-key="lib-photo-menu"
|
||||
style={{ left: photoMenu.x, top: photoMenu.y }}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key="lib-photo-open-explorer"
|
||||
onClick={async () => {
|
||||
const photo = photoMenu.photo;
|
||||
setPhotoMenu(null);
|
||||
const folderPath = photo.dir ? `${photo.folder}/${photo.dir}` : photo.folder;
|
||||
const fullPath = `${folderPath}/${photo.name}`;
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(fullPath);
|
||||
setNote(t('lib.copiedPath', { path: fullPath }));
|
||||
}
|
||||
} catch {}
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">📁</span>
|
||||
{t('lib.openExplorer')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key="lib-photo-open-location"
|
||||
onClick={() => {
|
||||
const photo = photoMenu.photo;
|
||||
setPhotoMenu(null);
|
||||
const targetNode = photo.dir ? `${photo.folder}/${photo.dir}` : photo.folder;
|
||||
setNode(targetNode);
|
||||
setUp(photo.id);
|
||||
const parts = targetNode.split('/');
|
||||
setExpanded((prev) => {
|
||||
const next = new Set(prev);
|
||||
for (let i = 1; i <= parts.length; i++) {
|
||||
next.add(parts.slice(0, i).join('/'));
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">🎯</span>
|
||||
{t('lib.openLocation')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key="lib-photo-open-studio"
|
||||
onClick={() => {
|
||||
const photo = photoMenu.photo;
|
||||
setPhotoMenu(null);
|
||||
void open(photo);
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">🎨</span>
|
||||
{t('lib.openStudio')}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,13 +441,11 @@ function invert3x3(m: number[][]): number[][] | null {
|
||||
|
||||
function getCamToSrgbMatrix(cd: any): number[][] {
|
||||
if (cd?.rgb_cam && Array.isArray(cd.rgb_cam) && cd.rgb_cam.length >= 3) {
|
||||
const m = [
|
||||
[cd.rgb_cam[0][0], cd.rgb_cam[0][1], cd.rgb_cam[0][2]],
|
||||
[cd.rgb_cam[1][0], cd.rgb_cam[1][1], cd.rgb_cam[1][2]],
|
||||
[cd.rgb_cam[2][0], cd.rgb_cam[2][1], cd.rgb_cam[2][2]],
|
||||
return [
|
||||
[cd.rgb_cam[0][0] || 0, cd.rgb_cam[0][1] || 0, cd.rgb_cam[0][2] || 0],
|
||||
[cd.rgb_cam[1][0] || 0, cd.rgb_cam[1][1] || 0, cd.rgb_cam[1][2] || 0],
|
||||
[cd.rgb_cam[2][0] || 0, cd.rgb_cam[2][1] || 0, cd.rgb_cam[2][2] || 0],
|
||||
];
|
||||
const inv = invert3x3(m);
|
||||
if (inv) return inv;
|
||||
}
|
||||
|
||||
if (cd?.cam_xyz && Array.isArray(cd.cam_xyz) && cd.cam_xyz.length >= 3) {
|
||||
|
||||
@@ -258,6 +258,10 @@ export const en: Dict = {
|
||||
'lib.subsHide': 'THIS FOLDER ONLY',
|
||||
'lib.subsHint': 'Frames in subfolders: the whole branch under the open folder, or only what that folder holds itself.',
|
||||
'lib.open': 'Open in the studio',
|
||||
'lib.openExplorer': 'Open in window explorer',
|
||||
'lib.openLocation': 'Open at location',
|
||||
'lib.openStudio': 'Open in studio',
|
||||
'lib.copiedPath': 'Copied location to clipboard: {path}',
|
||||
'lib.edited': 'edited',
|
||||
'lib.size': '{mb} MB',
|
||||
'lib.count': '{n} photos',
|
||||
|
||||
@@ -271,6 +271,10 @@ export const vi = {
|
||||
'lib.subsHint':
|
||||
'Ảnh trong thư mục con: hiện cả nhánh dưới thư mục đang chọn, hoặc chỉ ảnh của chính thư mục đó.',
|
||||
'lib.open': 'Mở trong studio',
|
||||
'lib.openExplorer': 'Open in window explorer',
|
||||
'lib.openLocation': 'Open at location',
|
||||
'lib.openStudio': 'Open in studio',
|
||||
'lib.copiedPath': 'Đã sao chép đường dẫn: {path}',
|
||||
'lib.edited': 'đã chỉnh',
|
||||
'lib.size': '{mb} MB',
|
||||
'lib.count': '{n} ảnh',
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user