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;
|
bool _scanning = true;
|
||||||
String _searchQuery = '';
|
String _searchQuery = '';
|
||||||
_Project? _selectedProject;
|
_Project? _selectedProject;
|
||||||
|
_Project? _customFolderProject;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
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 home = _getRealHome() ?? '';
|
||||||
final result = await FilePicker.platform.pickFiles(
|
final result = await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
@ -438,7 +500,23 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
ElevatedButton.icon(
|
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),
|
icon: const Icon(Icons.folder_open, size: 14),
|
||||||
label: const Text('Browse'),
|
label: const Text('Browse'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user