feat: Browse picks folder, Load picks single file, recursive .jsonl scan
- Browse button now opens a folder picker and recursively scans all subfolders for .jsonl files, displaying them in the session list - New Load button for loading a single .jsonl file (old Browse behavior) - Default path remains ~/.claude/projects on launch - Signed, notarized, stapled DMG
This commit is contained in:
parent
e963b2edcd
commit
5c693bf3d8
@ -100,6 +100,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
bool _scanning = true;
|
||||
String _searchQuery = '';
|
||||
_Project? _selectedProject;
|
||||
_Project? _customFolderProject;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -290,7 +291,68 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pickFile() async {
|
||||
Future<void> _pickFolder() async {
|
||||
final result = await FilePicker.platform.getDirectoryPath(
|
||||
dialogTitle: 'Select a folder containing Claude session files',
|
||||
);
|
||||
if (result != null) {
|
||||
await _scanFolder(result);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _scanFolder(String folderPath) async {
|
||||
setState(() {
|
||||
_scanning = true;
|
||||
_selectedProject = null;
|
||||
});
|
||||
|
||||
final sessions = <_SessionFile>[];
|
||||
|
||||
// Recursively scan for .jsonl files
|
||||
final dir = Directory(folderPath);
|
||||
if (await dir.exists()) {
|
||||
await _scanFolderRecursive(dir, sessions);
|
||||
}
|
||||
|
||||
sessions.sort((a, b) => b.stat.modified.compareTo(a.stat.modified));
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_customFolderProject = _Project(
|
||||
dirPath: folderPath,
|
||||
rawDirName: folderPath.split('/').last,
|
||||
displayName: folderPath.split('/').last,
|
||||
fullPath: folderPath,
|
||||
sessions: sessions,
|
||||
lastModified: sessions.isNotEmpty ? sessions.first.stat.modified : DateTime.now(),
|
||||
);
|
||||
_selectedProject = _customFolderProject;
|
||||
_scanning = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _scanFolderRecursive(Directory dir, List<_SessionFile> sessions) async {
|
||||
try {
|
||||
await for (final entity in dir.list()) {
|
||||
if (entity is File && entity.path.endsWith('.jsonl')) {
|
||||
try {
|
||||
final stat = entity.statSync();
|
||||
final fileName = entity.path.split('/').last;
|
||||
sessions.add(_SessionFile(
|
||||
file: entity,
|
||||
stat: stat,
|
||||
sessionId: fileName.replaceAll('.jsonl', ''),
|
||||
));
|
||||
} catch (_) {}
|
||||
} else if (entity is Directory) {
|
||||
await _scanFolderRecursive(entity, sessions);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _loadSingleFile() async {
|
||||
final home = _getRealHome() ?? '';
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
@ -438,7 +500,23 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _pickFile,
|
||||
onPressed: _loadSingleFile,
|
||||
icon: const Icon(Icons.insert_drive_file_outlined, size: 14),
|
||||
label: const Text('Load'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.surfaceLight,
|
||||
foregroundColor: AppColors.textPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
side: const BorderSide(color: AppColors.surfaceBorder),
|
||||
),
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _pickFolder,
|
||||
icon: const Icon(Icons.folder_open, size: 14),
|
||||
label: const Text('Browse'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user