이미지 미리보기 팝업창이 다른 카드에 마우스 오버되어도 유지되도록 수정

This commit is contained in:
2026-05-14 16:45:52 +09:00
parent 722e2abb65
commit 72b09a9554
3 changed files with 76 additions and 42 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ src/
├── drag-drop.js # Card drag-and-drop reorder (single and multi-card) ├── drag-drop.js # Card drag-and-drop reorder (single and multi-card)
├── selection.js # Rubber-band selection, Shift-click, Cmd+click ├── selection.js # Rubber-band selection, Shift-click, Cmd+click
├── pinch-zoom.js # Trackpad pinch to resize thumbnails (CSS --thumb-size, 150400px) ├── pinch-zoom.js # Trackpad pinch to resize thumbnails (CSS --thumb-size, 150400px)
└── hover-preview.js # Hover preview overlay └── image-preview.js # Hover preview overlay
``` ```
### IPC surface ### IPC surface
@@ -1,4 +1,5 @@
const DELAY = 1000; const SHOW_DELAY = 1000;
const HIDE_DELAY = 100;
const SVG_NS = 'http://www.w3.org/2000/svg'; const SVG_NS = 'http://www.w3.org/2000/svg';
const RADIUS = 20; const RADIUS = 20;
@@ -6,9 +7,11 @@ const CIRC = 2 * Math.PI * RADIUS; // ~125.66
let _el = null; let _el = null;
let _imgEl = null; let _imgEl = null;
let _timer = null; let _showTimer = null;
let _hideTimer = null;
let _visible = false; let _visible = false;
let _loaderEl = null; let _loaderEl = null;
let _activeCard = null;
export function init() { export function init() {
_el = document.createElement('div'); _el = document.createElement('div');
@@ -26,35 +29,75 @@ export function setupCard(card) {
function _onEnter() { function _onEnter() {
const card = this; const card = this;
clearTimeout(_timer); _activeCard = card;
_showLoader(card);
_timer = setTimeout(async () => { // 팝업이 사라지려고 대기 중이면 취소
_removeLoader(); if (_hideTimer) {
const thumb = card.querySelector('img.card-thumb'); clearTimeout(_hideTimer);
if (!thumb) return; _hideTimer = null;
// 아직 lazy-load가 안 된 경우 직접 로딩
let src = thumb.src;
if (!src) {
src = await window.api.readImage(card.dataset.filepath) || '';
if (src) thumb.src = src; // 카드에도 캐싱
} }
if (!src) return;
if (_visible) {
// 이미 팝업이 열려 있으면 즉시 내용 전환
_switchTo(card);
return;
}
// 팝업이 닫힌 상태 — 1초 대기 후 표시
clearTimeout(_showTimer);
_showLoader(card);
_showTimer = setTimeout(async () => {
_showTimer = null;
_removeLoader();
if (_activeCard !== card) return;
const src = await _loadSrc(card);
if (!src || _activeCard !== card) return;
_imgEl.src = src; _imgEl.src = src;
_visible = true; _visible = true;
_show(card); _show(card);
}, DELAY); }, SHOW_DELAY);
} }
function _onLeave() { function _onLeave() {
clearTimeout(_timer); if (!_visible) {
_timer = null; // 팝업 미표시 상태 — 로더·타이머만 정리
_visible = false; clearTimeout(_showTimer);
_showTimer = null;
_removeLoader(); _removeLoader();
return;
}
// 팝업 표시 중 — 0.1초 후 닫기 (다른 카드 진입 시 취소됨)
_hideTimer = setTimeout(() => {
_hideTimer = null;
_visible = false;
_el.style.display = 'none'; _el.style.display = 'none';
_el.style.visibility = 'hidden'; _el.style.visibility = 'hidden';
}, HIDE_DELAY);
}
// ── 팝업 내용 전환 (이미 열린 상태에서 카드 변경) ──
async function _switchTo(card) {
const src = await _loadSrc(card);
if (!src || _activeCard !== card) return;
_imgEl.src = src;
_show(card);
}
// ── 이미지 소스 반환 (카드 썸네일 캐시 우선) ──
async function _loadSrc(card) {
const thumb = card.querySelector('img.card-thumb');
let src = thumb?.src || '';
if (!src) {
src = await window.api.readImage(card.dataset.filepath) || '';
if (src && thumb) thumb.src = src;
}
return src;
} }
// ── 원형 로딩바 ────────────────────────────────── // ── 원형 로딩바 ──────────────────────────────────
@@ -66,26 +109,17 @@ function _showLoader(card) {
svg.classList.add('card-hover-loader'); svg.classList.add('card-hover-loader');
svg.setAttribute('viewBox', '0 0 48 48'); svg.setAttribute('viewBox', '0 0 48 48');
// 배경 원
const bg = document.createElementNS(SVG_NS, 'circle'); const bg = document.createElementNS(SVG_NS, 'circle');
bg.classList.add('loader-bg'); bg.classList.add('loader-bg');
bg.setAttribute('cx', '24'); bg.setAttribute('cx', '24'); bg.setAttribute('cy', '24'); bg.setAttribute('r', '24');
bg.setAttribute('cy', '24');
bg.setAttribute('r', '24');
// 트랙 링
const track = document.createElementNS(SVG_NS, 'circle'); const track = document.createElementNS(SVG_NS, 'circle');
track.classList.add('loader-track'); track.classList.add('loader-track');
track.setAttribute('cx', '24'); track.setAttribute('cx', '24'); track.setAttribute('cy', '24'); track.setAttribute('r', String(RADIUS));
track.setAttribute('cy', '24');
track.setAttribute('r', String(RADIUS));
// 진행 호
const progress = document.createElementNS(SVG_NS, 'circle'); const progress = document.createElementNS(SVG_NS, 'circle');
progress.classList.add('loader-progress'); progress.classList.add('loader-progress');
progress.setAttribute('cx', '24'); progress.setAttribute('cx', '24'); progress.setAttribute('cy', '24'); progress.setAttribute('r', String(RADIUS));
progress.setAttribute('cy', '24');
progress.setAttribute('r', String(RADIUS));
progress.style.strokeDasharray = CIRC; progress.style.strokeDasharray = CIRC;
progress.style.strokeDashoffset = CIRC; progress.style.strokeDashoffset = CIRC;
@@ -103,7 +137,7 @@ function _removeLoader() {
} }
} }
// ── 이미지 팝업 ────────────────────────────────── // ── 이미지 팝업 위치 계산 및 표시 ────────────────
function _show(card) { function _show(card) {
_el.style.display = 'block'; _el.style.display = 'block';
+1 -1
View File
@@ -2,7 +2,7 @@ import { state } from './state.js';
import * as pinchZoom from './handlers/pinch-zoom.js'; import * as pinchZoom from './handlers/pinch-zoom.js';
import * as selection from './handlers/selection.js'; import * as selection from './handlers/selection.js';
import * as dragDrop from './handlers/drag-drop.js'; import * as dragDrop from './handlers/drag-drop.js';
import * as hoverPreview from './handlers/hover-preview.js'; import * as hoverPreview from './handlers/image-preview.js';
let _cardObserver = null; let _cardObserver = null;