diff --git a/src/renderer/handlers/file-explorer.js b/src/renderer/handlers/file-explorer.js
index c3a5607..c1c0a0f 100644
--- a/src/renderer/handlers/file-explorer.js
+++ b/src/renderer/handlers/file-explorer.js
@@ -1,7 +1,8 @@
// Directory tree for the sidebar file-explorer pane.
// Lazy-loads children when a node is expanded. Tracks one selected path.
+// Row click = select + always expand. Chevron click = toggle collapse/expand.
-let _onSelect = null; // callback(folderPath)
+let _onSelect = null;
let _selectedPath = null;
const ICON_CLOSED = ``;
@@ -43,9 +44,9 @@ function createNode({ name, path: nodePath, hasChildren, isRoot = false }) {
const chevron = document.createElement('span');
chevron.className = 'tree-chevron';
- chevron.innerHTML = hasChildren
- ? ``
- : '';
+ if (hasChildren) {
+ chevron.innerHTML = ``;
+ }
const icon = document.createElement('span');
icon.className = 'tree-icon';
@@ -67,15 +68,32 @@ function createNode({ name, path: nodePath, hasChildren, isRoot = false }) {
wrap.appendChild(children);
}
- row.addEventListener('click', (e) => {
+ // Chevron click: toggle collapse/expand without changing selection
+ chevron.addEventListener('click', (e) => {
e.stopPropagation();
+ if (!hasChildren) return;
+ if (wrap.dataset.expanded === 'true') {
+ collapseNode(wrap);
+ } else {
+ expandNode(wrap, null);
+ }
+ });
+
+ // Row click: select + always expand (never collapse)
+ row.addEventListener('click', () => {
selectNode(wrap);
- if (hasChildren) toggleNode(wrap);
+ if (hasChildren && wrap.dataset.expanded !== 'true') {
+ expandNode(wrap, null);
+ }
});
return wrap;
}
+function nodeIcon(nodeEl) {
+ return nodeEl.dataset.isRoot === 'true' ? ICON_HOME : ICON_OPEN;
+}
+
function selectNode(nodeEl) {
const path = nodeEl.dataset.path;
document.querySelectorAll('.tree-row.active').forEach(r => r.classList.remove('active'));
@@ -84,20 +102,11 @@ function selectNode(nodeEl) {
if (_onSelect) _onSelect(path);
}
-async function toggleNode(nodeEl) {
- const expanded = nodeEl.dataset.expanded === 'true';
- if (expanded) {
- collapseNode(nodeEl);
- } else {
- await expandNode(nodeEl, null);
- }
-}
-
function collapseNode(nodeEl) {
nodeEl.dataset.expanded = 'false';
const row = nodeEl.querySelector(':scope > .tree-row');
row.querySelector('.tree-chevron').classList.remove('open');
- row.querySelector('.tree-icon').innerHTML = ICON_CLOSED;
+ row.querySelector('.tree-icon').innerHTML = nodeEl.dataset.isRoot === 'true' ? ICON_HOME : ICON_CLOSED;
const children = nodeEl.querySelector(':scope > .tree-children');
if (children) children.style.display = 'none';
}
@@ -106,41 +115,46 @@ async function expandNode(nodeEl, autoExpandPath) {
nodeEl.dataset.expanded = 'true';
const row = nodeEl.querySelector(':scope > .tree-row');
row.querySelector('.tree-chevron').classList.add('open');
- row.querySelector('.tree-icon').innerHTML = nodeEl.dataset.isRoot === 'true' ? ICON_HOME : ICON_OPEN;
+ row.querySelector('.tree-icon').innerHTML = nodeIcon(nodeEl);
const childrenEl = nodeEl.querySelector(':scope > .tree-children');
if (!childrenEl) return;
childrenEl.style.display = '';
- // Load children if not yet loaded
+ // Lazy-load children on first expansion
if (!childrenEl.dataset.loaded) {
childrenEl.dataset.loaded = 'true';
const dirs = await window.api.listDirectories(nodeEl.dataset.path);
- dirs.forEach(dir => {
- const child = createNode(dir);
- childrenEl.appendChild(child);
- });
+ dirs.forEach(dir => childrenEl.appendChild(createNode(dir)));
}
- // Auto-expand path to target
+ // Auto-expand toward the target path
if (autoExpandPath && autoExpandPath !== nodeEl.dataset.path) {
const childNodes = childrenEl.querySelectorAll(':scope > .tree-node');
for (const child of childNodes) {
- if (autoExpandPath === child.dataset.path || autoExpandPath.startsWith(child.dataset.path + '/')) {
+ const cp = child.dataset.path;
+ if (autoExpandPath === cp || autoExpandPath.startsWith(cp + '/')) {
await expandNode(child, autoExpandPath);
break;
}
}
}
+ // Reached the target — select it
if (autoExpandPath === nodeEl.dataset.path) {
selectNode(nodeEl);
}
}
-export function setSelectedPath(folderPath) {
+// Mark a path as selected and ensure the node is expanded to show its children.
+// Called from renderer when a folder is loaded externally (e.g. "폴더 열기" dialog).
+export async function setSelectedPath(folderPath) {
_selectedPath = folderPath;
document.querySelectorAll('.tree-row.active').forEach(r => r.classList.remove('active'));
const node = document.querySelector(`.tree-node[data-path="${CSS.escape(folderPath)}"]`);
- if (node) node.querySelector(':scope > .tree-row').classList.add('active');
+ if (!node) return;
+ node.querySelector(':scope > .tree-row').classList.add('active');
+ if (node.dataset.hasChildren === 'true' && node.dataset.expanded !== 'true') {
+ await expandNode(node, null);
+ }
}
diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js
index 862d63f..cb3610a 100644
--- a/src/renderer/renderer.js
+++ b/src/renderer/renderer.js
@@ -100,7 +100,7 @@ async function loadFolder(folder) {
state.currentFolder = folder;
dirPath.textContent = folder;
await window.api.setLastFolder(folder);
- fileExplorer.setSelectedPath(folder);
+ await fileExplorer.setSelectedPath(folder);
showLoading('이미지 목록 불러오는 중...');
const list = await window.api.listImages(folder);
diff --git a/src/renderer/style.css b/src/renderer/style.css
index 8740f68..887a0d0 100644
--- a/src/renderer/style.css
+++ b/src/renderer/style.css
@@ -450,9 +450,12 @@ footer span { font-size: 11px; color: var(--muted); }
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; color: var(--muted);
transition: transform .15s;
+ cursor: pointer;
+ border-radius: 3px;
}
+.tree-chevron:hover { background: rgba(255,255,255,.08); color: var(--text); }
.tree-chevron.open { transform: rotate(90deg); }
-.tree-chevron svg { display: block; }
+.tree-chevron svg { display: block; pointer-events: none; }
.tree-icon {
width: 16px; height: 16px;