import { useEffect, useRef, useState } from 'preact/hooks';
import { listDir } from '../api';
import { useT } from '../settings';
interface FilePickerProps {
current: string;
onPick: (path: string) => void;
onClose: () => void;
}
function parseBreadcrumb(path: string): { label: string; fullPath: string }[] {
const clean = path.replace(/\/+$/, '');
if (!clean || clean === '/') return [{ label: '/', fullPath: '/' }];
const parts = clean.split('/').filter(Boolean);
const crumbs: { label: string; fullPath: string }[] = [{ label: '/', fullPath: '/' }];
let acc = '';
for (const p of parts) {
acc += '/' + p;
crumbs.push({ label: p, fullPath: acc });
}
return crumbs;
}
export function FilePicker({ current, onPick, onClose }: FilePickerProps) {
const t = useT();
const [browsePath, setBrowsePath] = useState(current || '~');
const [dirs, setDirs] = useState<string[]>([]);
const [files, setFiles] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const resolvedPath = useRef(current || '');
useEffect(() => {
setLoading(true);
setError(null);
listDir(browsePath)
.then((result) => {
resolvedPath.current = result.path;
if (result.path !== browsePath) setBrowsePath(result.path);
setDirs(result.dirs);
setFiles(result.files ?? []);
})
.catch((e: unknown) => {
setError(e instanceof Error ? e.message : String(e));
setDirs([]);
setFiles([]);
})
.finally(() => setLoading(false));
}, [browsePath]);
function enterDir(name: string) {
setBrowsePath(resolvedPath.current.replace(/\/+$/, '') + '/' + name);
}
function pickFile(name: string) {
onPick(resolvedPath.current.replace(/\/+$/, '') + '/' + name);
onClose();
}
const crumbs = parseBreadcrumb(browsePath);
return (
<div
class="modal-overlay"
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div class="modal-card">
<div class="modal-header">
<span>📄</span>
<h3>{t('filepicker.title')}</h3>
</div>
<div class="modal-body">
<div class="dir-browser">
<div class="dir-breadcrumb">
{crumbs.map((crumb, i) => (
<span key={i}>
<span
onClick={() => setBrowsePath(crumb.fullPath)}
class={'dir-crumb' + (i === crumbs.length - 1 ? ' current' : '')}
>
{crumb.label}
</span>
{i < crumbs.length - 1 && <span> / </span>}
</span>
))}
</div>
<div class="dir-list">
{loading && <div class="dir-note">{t('filepicker.loading')}</div>}
{error && <div class="dir-note error">{error}</div>}
{!loading && !error && dirs.length === 0 && files.length === 0 && (
<div class="dir-note">{t('filepicker.empty')}</div>
)}
{!loading &&
dirs.map((d) => (
<button key={'d:' + d} class="dir-item" onClick={() => enterDir(d)}>
<span>📁</span>
<span>{d}</span>
</button>
))}
{!loading &&
files.map((f) => (
<button key={'f:' + f} class="dir-item file-item" onClick={() => pickFile(f)}>
<span>📄</span>
<span>{f}</span>
</button>
))}
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn" onClick={onClose}>
{t('common.cancel')}
</button>
</div>
</div>
</div>
);
}