// ============================================================================= // Name: File Explorer // License: CC-BY-NC-4.0 // Repository: https://github.com/CodyTolene/pip-apps // Description: Directory and media file explorer for the Pip-Boy 3000 Mk V. // Version: 1.2.0 // ============================================================================= var fs = require('fs'); const SCREEN_WIDTH = g.getWidth(); const SCREEN_HEIGHT = g.getHeight(); const COLOR_ACTIVE = '#0F0'; const COLOR_TEXT = '#FFF'; const LINE_HEIGHT = 12; const MAX_LINES = Math.floor(SCREEN_HEIGHT / LINE_HEIGHT); const ROOT_PATH = '/'; let entries = []; let currentIndex = 0; let scrollOffset = 0; let currentAudio = null; let isVideoPlaying = false; function resolvePath(dir, file) { if (dir === '/' || dir === '') return '/' + file; return dir + '/' + file; } function loadDirectory(dir) { try { let list = fs.readdir(dir) || []; let depth = dir.split('/').length - 1; entries = []; if (dir !== ROOT_PATH) { entries.push({ name: '..', path: dir.split('/').slice(0, -1).join('/') || '/', type: 'dir', depth: depth - 1, }); } list.forEach((name) => { let path = resolvePath(dir, name); let type = 'file'; try { fs.readdir(path); type = 'dir'; } catch (_) {} entries.push({ name: name, path: path, type: type, depth: depth, }); }); currentIndex = 0; scrollOffset = 0; drawUI(); } catch (e) { console.log('Failed to load dir:', dir); } } function drawUI() { g.clear(); g.setColor(COLOR_TEXT); g.setFont('6x8', 2); g.drawString('File Explorer', SCREEN_WIDTH / 2, 10); g.setFont('6x8', 1); let visible = entries.slice(scrollOffset, scrollOffset + MAX_LINES); visible.forEach(function (entry, i) { let isSelected = scrollOffset + i === currentIndex; g.setColor(isSelected ? COLOR_ACTIVE : COLOR_TEXT); let indent = ''; for (let d = 0; d < entry.depth; d++) { indent += '...'; } let tag = entry.type === 'dir' ? '[DIR] ' : '[FILE] '; let label = indent + tag + entry.name; let labelWidth = g.stringWidth(label); let x = 60 + labelWidth / 2; let y = 20 + i * LINE_HEIGHT; g.drawString(label, x, y); if (entry.path === currentAudio) { g.drawString(' (PLAYING)', x + labelWidth + 4, y); } }); } function scrollUp() { stopVideo(); if (currentIndex > 0) currentIndex--; if (currentIndex < scrollOffset) scrollOffset--; drawUI(); } function scrollDown() { stopVideo(); if (currentIndex < entries.length - 1) currentIndex++; if (currentIndex >= scrollOffset + MAX_LINES) scrollOffset++; drawUI(); } function handleInput() { if (BTN_TUNEUP.read()) { scrollUp(); } if (BTN_TUNEDOWN.read()) { scrollDown(); } if (BTN_PLAY.read()) { selectEntry(); } if (BTN_TORCH.read()) { Pip.audioStop(); Pip.videoStop(); currentAudio = null; isVideoPlaying = false; E.reboot(); } } function selectEntry() { const selected = entries[currentIndex]; if (!selected) return; if (selected.type === 'dir') { loadDirectory(selected.path); return; } const name = selected.name.toLowerCase(); if (isVideoPlaying) { stopVideo(); return; } if (name.endsWith('.wav') || name.endsWith('.mp3') || name.endsWith('.ogg')) { if (currentAudio === selected.path) { Pip.audioStop(); currentAudio = null; drawUI(); } else { Pip.audioStop(); Pip.audioStart(selected.path); currentAudio = selected.path; drawUI(); } return; } if (name.endsWith('.avi') || name.endsWith('.mp4')) { Pip.audioStop(); Pip.videoStart(selected.path, { x: 40, y: 0 }); isVideoPlaying = true; currentAudio = null; return; } if (name.endsWith('.holotape')) { Pip.loadApp(selected.path); return; } } function stopVideo() { if (isVideoPlaying) { Pip.videoStop(); isVideoPlaying = false; drawUI(); } } function showLoadingScreen() { g.clear(); g.setFont('6x8', 2); g.setColor(COLOR_TEXT); g.drawString('Loading...', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); } function startExplorer() { if (!Pip.isSDCardInserted()) { g.clear(); g.setFont('6x8', 2); g.setColor('#F00'); g.drawString('NO SD CARD DETECTED', SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); return; } showLoadingScreen(); setTimeout(function () { loadDirectory(ROOT_PATH); setInterval(handleInput, 150); }, 50); Pip.removeAllListeners('knob1'); Pip.on('knob1', function (dir) { if (dir < 0) scrollDown(); else if (dir > 0) scrollUp(); else selectEntry(); }); Pip.removeAllListeners('knob2'); Pip.on('knob2', function (dir) { if (dir < 0) scrollUp(); else if (dir > 0) scrollDown(); }); } startExplorer();