import React, { useState, useEffect } from 'react'; import { supabase } from '../supabaseClient'; interface ProblemFile { id: string; name: string; pattern: string; difficulty: string; reference_code: string; description: string | null; box_level: number | null; next_review: string | null; last_reviewed: string | null; times_correct: number; total_attempts: number; } interface FileExplorerProps { onSetWallpaper: (url: string) => void; activeWallpaperUrl: string; } const WALLPAPER_PRESETS = [ { name: 'Retro Arcade Grid.img', url: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAMJHVhhK8_G7F8sbhn8F7w4AZWXb1O_-HLKvrZ_fJlbUVMcVuxxEOO_LlOh8qIwtAn2KvzvCwzmtNLLEZ5uYkCEsx8ZWXJR609qgSMRSX8LBBeskk4VzVXnyzsgIKCedeV2PvGxJlUNnledcWKCXqG9egQi8dgTcA7C2z82QyM73KZ4s7ZRzZHNupuQt1ocfcMl9E_x1PWFRridl751LIpyGZgednS5CmVw2rZvFc_tbp2QTxgVHJ_59myEBmy6aajbO06AhkKmCvI', desc: 'Default AlgoSpaced purple grid and pixel theme background.' }, { name: 'Classic Board Logo.img', url: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD2tUGA2d7QiNyajDAWYK183zhNTgtAZpOXK3E9wRvHGs-t6ykWt9uPScYe_fziWzQI0pREcY_ThI341WvGMusyYmnAkagfuwx6wubIs1ES68DO8CCNAlcHcb2zOUU4MJeYuhWDy1uYRqyGYjIaDUqfgNWk2vm4WzwRqoorn2dxtZ6QdJhEKbXjG8fG9chkkvr68qJf0fLUL6bIqBXZg2FJ30S7zS3oZ4IZsug-wLyRYuJ4Gu_86snNUo1whNrBdZm5OMREfc9sLbwJ', desc: 'System info and arcade machine diagram wallpaper.' }, { name: 'Terminal Console Icon.img', url: 'https://lh3.googleusercontent.com/aida-public/AB6AXuA2AvcQ2oKWP8AJ8yR5nF82LLDEH1FKySUK-npnqEjvJ6Cs9xp8joCwH7x43oTCAC5xhO2rQdYzka3B6u72FNbdxMnsv3WJLye3bKochu8AnnRlS3szfYSaj724sYsiZo4n8G-oWqvl7C0rOGaDaU2nRH1xXmW2PBi6L2NryTsUaTCVZQKTKBBNNEOig5kh7B52e-iIuj2PTzaSjF9ebp6gMuCnBtJT8Sxo5Qsmvfm-t2U5r_qQpRiYj--Ut41T7NTh-tHgs4S4kBvz', desc: 'Dark retro CRT terminal screen layout.' }, { name: 'Synthwave Sunset Grid.img', url: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?q=80&w=1000&auto=format&fit=crop', desc: 'Glowing neon grids and vector mountains styling.' }, { name: 'Matrix Code Rain.img', url: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?q=80&w=1000&auto=format&fit=crop', desc: 'Falling green digital characters rain.' } ]; export default function FileExplorer({ onSetWallpaper, activeWallpaperUrl }: FileExplorerProps) { const [currentPath, setCurrentPath] = useState(['C:']); const [problems, setProblems] = useState([]); const [loading, setLoading] = useState(true); const [selectedItem, setSelectedItem] = useState(null); // Dynamic wallpapers list merging presets and custom wallpaper from localStorage const wallpapersList = React.useMemo(() => { const list = [...WALLPAPER_PRESETS]; const customUrl = localStorage.getItem('customWallpaperUrl'); const customName = localStorage.getItem('customWallpaperName') || 'Custom Wallpaper.img'; if (customUrl) { const exists = list.some(p => p.url === customUrl); if (!exists) { list.push({ name: customName, url: customUrl, desc: 'User uploaded custom background wallpaper.' }); } } return list; }, [activeWallpaperUrl]); // Search & Filter state const [searchQuery, setSearchQuery] = useState(''); const [difficultyFilter, setDifficultyFilter] = useState('All'); const [patternFilter, setPatternFilter] = useState('All'); // Drag & Drop state const [dragOver, setDragOver] = useState(false); // New File Creation state const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [newFileName, setNewFileName] = useState(''); const [newFilePattern, setNewFilePattern] = useState(''); const [newFileDifficulty, setNewFileDifficulty] = useState('Medium'); const [newFileCode, setNewFileCode] = useState(''); const [newFileDescription, setNewFileDescription] = useState(''); const [createError, setCreateError] = useState(''); const [creating, setCreating] = useState(false); // Previewer Modal state const [previewFile, setPreviewFile] = useState(null); const [modalTab, setModalTab] = useState<'description' | 'code' | 'properties'>('description'); const [isEditing, setIsEditing] = useState(false); const [editedCode, setEditedCode] = useState(''); const [editedDescription, setEditedDescription] = useState(''); const [saving, setSaving] = useState(false); const [saveSuccess, setSaveSuccess] = useState(false); const [copied, setCopied] = useState(false); const fetchProblems = async () => { setLoading(true); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/explorer/problems`, { headers: { 'Authorization': `Bearer ${session.access_token}` } }); if (res.ok) { const data = await res.json(); setProblems(data); } } catch (err) { console.error('Failed to fetch explorer files:', err); } finally { setLoading(false); } }; useEffect(() => { fetchProblems(); }, []); const navigateTo = (folder: string) => { setCurrentPath(['C:', folder]); setSelectedItem(null); }; const navigateUp = () => { if (currentPath.length > 1) { setCurrentPath(['C:']); setSelectedItem(null); } }; // ZIP Solutions Exporter via Backend stream const handleExportZip = async () => { try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/explorer/export`, { headers: { 'Authorization': `Bearer ${session.access_token}` } }); if (res.ok) { const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'algospaced_solutions.zip'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } } catch (err) { console.error('Failed to download solutions ZIP:', err); } }; // Handle Save Solution Code changes const handleSaveSolution = async () => { if (!previewFile) return; setSaving(true); setSaveSuccess(false); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/explorer/problems/${previewFile.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` }, body: JSON.stringify({ name: previewFile.name, pattern: previewFile.pattern, difficulty: previewFile.difficulty, reference_code: editedCode, description: editedDescription }) }); if (res.ok) { setSaveSuccess(true); setIsEditing(false); // Refresh local problems list const updated = problems.map(p => p.id === previewFile.id ? { ...p, reference_code: editedCode, description: editedDescription } : p); setProblems(updated); setPreviewFile({ ...previewFile, reference_code: editedCode, description: editedDescription }); setTimeout(() => setSaveSuccess(false), 2000); } } catch (err) { console.error('Failed to update solution code:', err); } finally { setSaving(false); } }; // Create New Problem const handleCreateProblem = async (e: React.FormEvent) => { e.preventDefault(); if (!newFileName.trim()) { setCreateError('Problem name cannot be empty'); return; } setCreating(true); setCreateError(''); try { const { data: { session } } = await supabase.auth.getSession(); if (!session) return; const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/explorer/problems/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` }, body: JSON.stringify({ name: newFileName.trim(), pattern: newFilePattern.trim() || 'General', difficulty: newFileDifficulty, reference_code: newFileCode, description: newFileDescription }) }); const data = await res.json(); if (res.ok && data.success) { setIsCreateModalOpen(false); setNewFileName(''); setNewFilePattern(''); setNewFileCode(''); setNewFileDescription(''); fetchProblems(); } else { setCreateError(data.detail || 'Failed to create problem.'); } } catch { setCreateError('Connection timed out.'); } finally { setCreating(false); } }; // Drag and Drop files parser const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setDragOver(true); }; const handleDragLeave = () => { setDragOver(false); }; const handleDrop = async (e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const files = e.dataTransfer.files; if (files.length > 0) { const file = files[0]; if (file.name.endsWith('.py')) { try { const text = await file.text(); const guessedName = file.name .replace('.py', '') .replace(/_/g, ' ') .replace(/-/g, ' '); // Parse metadata tags if they exist inside script comments let pattern = 'General'; let difficulty = 'Medium'; const patternMatch = text.match(/#\s*Pattern:\s*(.+)/i); if (patternMatch) pattern = patternMatch[1].trim(); const difficultyMatch = text.match(/#\s*Difficulty:\s*(.+)/i); if (difficultyMatch) { const val = difficultyMatch[1].trim(); if (['Easy', 'Medium', 'Hard'].includes(val)) difficulty = val; } // Parse description: take all leading comment lines (excluding structural tags) const lines = text.split('\n'); let descLines = []; for (let line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('#')) { if (trimmed.match(/#\s*(Pattern|Difficulty|Problem|Description):/i)) continue; descLines.push(trimmed.replace(/^#\s*/, '')); } else if (trimmed !== '') { break; } } const description = descLines.join('\n').trim(); // Load into New File Creator modal setNewFileName(guessedName); setNewFilePattern(pattern); setNewFileDifficulty(difficulty); setNewFileCode(text); setNewFileDescription(description); setIsCreateModalOpen(true); } catch (err) { console.error('Failed to parse uploaded script:', err); } } } }; const handleCopyCode = (text: string) => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const activeFolder = currentPath.length > 1 ? currentPath[1] : 'root'; // Statistics calculation for the visualizer const totalProblems = problems.length; const completedSolutions = problems.filter(p => p.reference_code && p.reference_code.trim()).length; const completionPercent = totalProblems > 0 ? Math.round((completedSolutions / totalProblems) * 100) : 0; const boxCounts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; problems.forEach(p => { if (p.box_level !== null && p.box_level !== undefined) { const lvl = p.box_level as 1|2|3|4|5; if (lvl >= 1 && lvl <= 5) boxCounts[lvl]++; } }); // Extract unique patterns to populate filter dropdown dynamically const uniquePatterns = Array.from(new Set(problems.map(p => p.pattern).filter(Boolean))); // Filter solutions list const filteredProblems = problems.filter(p => { const matchesSearch = p.name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesDifficulty = difficultyFilter === 'All' || p.difficulty === difficultyFilter; const matchesPattern = patternFilter === 'All' || p.pattern === patternFilter; return matchesSearch && matchesDifficulty && matchesPattern; }); return (
{/* 1. Address Bar & Navigation */}
{currentPath.join('\\')}
{/* Action Link bar */}
{activeFolder === 'Database_Codes' && ( <> | | )} {activeFolder === 'root' ? '2 Folder(s)' : activeFolder === 'Database_Codes' ? `${filteredProblems.length} of ${problems.length} item(s)` : `${wallpapersList.length} image(s)`}
{/* 2. Main Workspace Layout */}
{/* Left Side: Navigation Sidebar & Disk Stats Visualizer */} {/* Right Side: Folder View Contents & Upload Area */}
{dragOver && (
upload_file
DROP SOLUTIONS PYTHON SCRIPT (.PY) HERE

Will load and parse file metadata for import

)} {loading ? (
READING SYSTEM DISK...
) : ( <> {/* Feature 2: Search and Filter Bar Header */} {activeFolder === 'Database_Codes' && (
{/* Search Field */}
search setSearchQuery(e.target.value)} className="w-full bg-transparent outline-none border-none p-0 text-[11px] focus:ring-0 font-sans" placeholder="Search codes by name..." />
{/* Difficulty Filter */}
Diff:
{/* Pattern Filter */}
Pattern:
)} {/* Root Content View */} {activeFolder === 'root' && (
navigateTo('Database_Codes')} onClick={() => setSelectedItem('codes')} className={`flex flex-col items-center justify-center p-3 border-[3px] border-transparent hover:bg-slate-50 cursor-pointer rounded select-none ${selectedItem === 'codes' ? 'bg-yellow-50 border-dashed border-ink-black' : ''}`} > folder Database_Codes Synced solution files
navigateTo('Wallpapers')} onClick={() => setSelectedItem('wallpapers')} className={`flex flex-col items-center justify-center p-3 border-[3px] border-transparent hover:bg-slate-50 cursor-pointer rounded select-none ${selectedItem === 'wallpapers' ? 'bg-yellow-50 border-dashed border-ink-black' : ''}`} > folder Wallpapers Desktop wallpaper images
)} {/* Database Codes Content View */} {activeFolder === 'Database_Codes' && ( filteredProblems.length === 0 ? (
drafts No matching files found. Drag and drop a .py script to upload/create!
) : (
{filteredProblems.map((prob) => { const cleanName = prob.name.replace(/\s+/g, '_'); const fileName = `${cleanName}.py`; const isSelected = selectedItem === prob.id; return (
setSelectedItem(prob.id)} onDoubleClick={() => { setPreviewFile(prob); setEditedCode(prob.reference_code || ''); setEditedDescription(prob.description || ''); setModalTab('description'); setIsEditing(false); }} className={`flex flex-col items-center p-2 border-[3px] border-transparent hover:bg-slate-50 cursor-pointer rounded select-none text-center ${isSelected ? 'bg-yellow-50 border-dashed border-ink-black' : ''}`} > article {fileName} {prob.difficulty}
); })}
) )} {/* Wallpapers Content View */} {activeFolder === 'Wallpapers' && (
{wallpapersList.map((paper) => { const isSelected = selectedItem === paper.name; const isActive = activeWallpaperUrl === paper.url; return (
setSelectedItem(paper.name)} className={`border-[3px] p-2 flex flex-col cursor-pointer bg-slate-50 relative hover:bg-slate-100 ${isSelected ? 'border-ink-black bg-yellow-50 shadow-[2px_2px_0_0_#1E293B]' : 'border-slate-300'}`} >
{paper.name} {isActive && (
check
)}
{paper.name}

{paper.desc}

{isSelected && ( )}
); })}
)} )}
{/* Feature 3 & 1: Two-Tab File Previewer & Editor Inspector Modal */} {previewFile && (
{/* Modal Title bar */}
article {previewFile.name.replace(/\s+/g, '_')}.py
{/* Triple Tab selectors */}
{/* TAB CONTENT: Problem Description */} {modalTab === 'description' && (
{isEditing ? (