feat(browser): Implement lazy loading and E2E tests for browser
This commit is contained in:
parent
3a2e21e4b3
commit
94db42d2ad
122
internal/browser/lazy_loading_test.go
Normal file
122
internal/browser/lazy_loading_test.go
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"pad/internal/io/pool"
|
||||
"pad/internal/io/pool/mock"
|
||||
)
|
||||
|
||||
// TestLazyLoadingLargeDirectory verifies that page-based loading works
|
||||
// correctly for large directories (10,000 entries), ensuring only visible
|
||||
// and prefetched pages are kept in memory.
|
||||
func TestLazyLoadingLargeDirectory(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.TotalEntries = 10000
|
||||
state.EntryHeight = 48.0
|
||||
state.VisibleCount = 10
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(4)
|
||||
wp.Start()
|
||||
|
||||
// Populate mock filesystem with 10k entries
|
||||
fs.AddDir("/dir", time.Now())
|
||||
for i := 0; i < 10000; i++ {
|
||||
fs.AddFile(fmt.Sprintf("/dir/file_%d.txt", i), []byte("data"), time.Now())
|
||||
}
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// 1. Navigate to directory
|
||||
bm.NavigateTo("/dir")
|
||||
|
||||
// Helper to get result with timeout
|
||||
getResult := func(timeout time.Duration) (pool.Result, bool) {
|
||||
select {
|
||||
case res := <-wp.ResultChan():
|
||||
return res, true
|
||||
case <-time.After(timeout):
|
||||
return pool.Result{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for index build result and process it
|
||||
res, ok := getResult(2 * time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for BuildIndex result")
|
||||
}
|
||||
bm.HandleResult(res)
|
||||
|
||||
// Wait for initial page load result and process it
|
||||
res, ok = getResult(2 * time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for initial LoadPages result")
|
||||
}
|
||||
bm.HandleResult(res)
|
||||
|
||||
// 2. Verify only initial pages loaded (visible + prefetch)
|
||||
if len(state.Pages) == 0 {
|
||||
t.Error("Expected pages to be loaded")
|
||||
}
|
||||
|
||||
// 3. Scroll down to page 50
|
||||
state.ScrollOffset = 50 * 100 * state.EntryHeight // Scroll to middle
|
||||
bm.OnScroll()
|
||||
|
||||
// Wait for load pages result for scroll
|
||||
res, ok = getResult(2 * time.Second)
|
||||
if !ok {
|
||||
t.Fatal("Timeout waiting for scroll LoadPages result")
|
||||
}
|
||||
bm.HandleResult(res)
|
||||
|
||||
// 5. Verify old pages are evicted (e.g., page 0)
|
||||
if page, ok := state.Pages[0]; ok && page.Loaded {
|
||||
t.Error("Expected page 0 to be evicted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrefetchOnScroll verifies that scroll triggers prefetch for
|
||||
// pages within PrefetchDist.
|
||||
func TestPrefetchOnScroll(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.TotalEntries = 1000
|
||||
state.EntryHeight = 48.0
|
||||
state.VisibleCount = 10
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(4)
|
||||
wp.Start()
|
||||
|
||||
// Populate mock filesystem
|
||||
fs.AddDir("/dir", time.Now())
|
||||
for i := 0; i < 1000; i++ {
|
||||
fs.AddFile(fmt.Sprintf("/dir/file_%d.txt", i), []byte("data"), time.Now())
|
||||
}
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
bm.NavigateTo("/dir")
|
||||
// Process BuildIndex and initial load
|
||||
bm.HandleResult(<-wp.ResultChan())
|
||||
bm.HandleResult(<-wp.ResultChan())
|
||||
|
||||
// Current visible ~page 0. Pages 0, 1, 2 should be loaded.
|
||||
// Scroll to page 5.
|
||||
state.ScrollOffset = 5 * 100 * state.EntryHeight
|
||||
bm.OnScroll()
|
||||
// Process prefetch load
|
||||
bm.HandleResult(<-wp.ResultChan())
|
||||
|
||||
// Prefetch should load pages around 5 (e.g., 3, 4, 5, 6, 7)
|
||||
if _, ok := state.Pages[6]; !ok {
|
||||
t.Error("Expected page 6 to be prefetched")
|
||||
}
|
||||
}
|
||||
153
internal/browser/manager.go
Normal file
153
internal/browser/manager.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"pad/internal/io/pool"
|
||||
"pad/internal/io/pool/mock"
|
||||
)
|
||||
|
||||
var ErrDirNotFound = fmt.Errorf("directory not found")
|
||||
|
||||
type BrowserManager struct {
|
||||
state *BrowserState
|
||||
workerPool *pool.WorkerPool
|
||||
fs *mock.FileSystem
|
||||
dirPath string
|
||||
}
|
||||
|
||||
func NewBrowserManager(state *BrowserState, wp *pool.WorkerPool, fs *mock.FileSystem) (*BrowserManager, error) {
|
||||
return &BrowserManager{
|
||||
state: state,
|
||||
workerPool: wp,
|
||||
fs: fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) NavigateTo(dirPath string) {
|
||||
fmt.Printf("NavigateTo: %s\n", dirPath)
|
||||
bm.dirPath = dirPath
|
||||
navigateToDirectory(bm.state, dirPath)
|
||||
bm.state.Loading = true
|
||||
|
||||
// Dispatch BuildIndexTask to the worker pool.
|
||||
// In a real app, this would be a task that reads and sorts the directory.
|
||||
task := pool.NewBuildIndexTask(dirPath, bm.fs)
|
||||
bm.workerPool.Dispatch(task)
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) OnScroll() {
|
||||
if bm.state.TotalEntries == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Identify unloaded pages in the visible + prefetch range
|
||||
minPage, maxPage := computeVisiblePageRange(bm.state)
|
||||
var pagesToLoad []int
|
||||
for i := minPage; i <= maxPage; i++ {
|
||||
if page, ok := bm.state.Pages[i]; !ok || !page.Loaded {
|
||||
pagesToLoad = append(pagesToLoad, i)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("OnScroll: minPage=%d, maxPage=%d, pagesToLoad=%v\n", minPage, maxPage, pagesToLoad)
|
||||
|
||||
// Dispatch LoadPagesTask for unloaded pages
|
||||
if len(pagesToLoad) > 0 {
|
||||
fmt.Printf("OnScroll: Dispatching LoadPagesTask for pages %v\n", pagesToLoad)
|
||||
task := pool.NewLoadPagesTask(bm.dirPath, pagesToLoad, bm.fs)
|
||||
bm.workerPool.Dispatch(task)
|
||||
}
|
||||
|
||||
// Evict distant pages to save memory
|
||||
bm.state.EvictPages()
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) HandleResult(result pool.Result) {
|
||||
if !result.IsBrowserResult() {
|
||||
return
|
||||
}
|
||||
|
||||
switch result.TaskType {
|
||||
case pool.TypeBuildIndex:
|
||||
if result.Success {
|
||||
bm.handleBuildIndexSuccess(result)
|
||||
} else {
|
||||
bm.handleError(result)
|
||||
}
|
||||
case pool.TypeLoadPages:
|
||||
if result.Success {
|
||||
bm.handleLoadPagesSuccess(result)
|
||||
} else {
|
||||
bm.handleError(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) handleBuildIndexSuccess(result pool.Result) {
|
||||
fmt.Printf("handleBuildIndexSuccess: TotalEntries=%d\n", len(result.Data.([]mock.DirEntry)))
|
||||
bm.state.Loading = false
|
||||
|
||||
// In the mock implementation, BuildIndexTask returns []mock.DirEntry.
|
||||
// We need to convert these to browser.Entry and build the SortIndex.
|
||||
entries := result.Data.([]mock.DirEntry)
|
||||
|
||||
var browserEntries []Entry
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
browserEntries = append(browserEntries, Entry{
|
||||
Path: filepath.Join(bm.dirPath, e.Name()),
|
||||
Name: e.Name(),
|
||||
Size: info.Size(),
|
||||
ModTime: info.ModTime(),
|
||||
IsDir: e.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
// Build the sorted index with position maps
|
||||
bm.state.SortIndex = &DirectoryIndex{
|
||||
Path: bm.dirPath,
|
||||
EntryCount: len(browserEntries),
|
||||
Entries: browserEntries,
|
||||
SortOrders: make(map[string][]int),
|
||||
}
|
||||
|
||||
// Compute position maps for all sort modes
|
||||
for mode := SortMode(0); mode < SortMode(modeCount()); mode++ {
|
||||
key := sortModeKey(mode)
|
||||
if key != "" {
|
||||
bm.state.SortIndex.SortOrders[key] = buildPositionMap(browserEntries, mode)
|
||||
}
|
||||
}
|
||||
|
||||
bm.state.TotalEntries = len(browserEntries)
|
||||
fmt.Printf("handleBuildIndexSuccess: TotalEntries set to %d\n", bm.state.TotalEntries)
|
||||
|
||||
// Trigger initial page load
|
||||
bm.OnScroll()
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) handleLoadPagesSuccess(result pool.Result) {
|
||||
// In a real implementation, LoadPagesTask would return the actual entry data.
|
||||
// In our current mock/placeholder, it just returns the page indices.
|
||||
// We'll load the data from the SortIndex.
|
||||
pageIndices := result.Data.([]int)
|
||||
|
||||
for _, idx := range pageIndices {
|
||||
page := loadPageFromIndex(bm.state, idx)
|
||||
if page != nil {
|
||||
bm.state.Pages[idx] = page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *BrowserManager) handleError(result pool.Result) {
|
||||
fmt.Printf("handleError: TaskType=%s, Error=%v\n", result.TaskType, result.Error)
|
||||
bm.state.Loading = false
|
||||
// For now, just reset the state or log the error.
|
||||
// In production, we'd show a toast or error message.
|
||||
if bm.state.CurrentPath == bm.dirPath {
|
||||
bm.state.Reset()
|
||||
}
|
||||
}
|
||||
305
internal/browser/manager_test.go
Normal file
305
internal/browser/manager_test.go
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"pad/internal/io/pool"
|
||||
"pad/internal/io/pool/mock"
|
||||
)
|
||||
|
||||
// TestNewBrowserManager verifies that a BrowserManager can be constructed
|
||||
// with valid parameters and has correct initial state.
|
||||
func TestNewBrowserManager(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
if bm == nil {
|
||||
t.Fatal("NewBrowserManager returned nil")
|
||||
}
|
||||
|
||||
// Verify initial state
|
||||
if bm.state != state {
|
||||
t.Errorf("BrowserManager.state = %p, want %p", bm.state, state)
|
||||
}
|
||||
|
||||
if bm.dirPath != "" {
|
||||
t.Errorf("BrowserManager.dirPath = %q, want empty string", bm.dirPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNavigateTo verifies that NavigateTo dispatches a BuildIndexTask
|
||||
// and resets browser state appropriately.
|
||||
func TestNavigateTo(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
// Create some mock entries in the filesystem
|
||||
now := time.Now()
|
||||
fs.AddDir("/test/docs", now)
|
||||
fs.AddFile("/test/docs/file1.txt", []byte("content1"), now)
|
||||
fs.AddFile("/test/docs/file2.txt", []byte("content2"), now)
|
||||
fs.AddDir("/test/docs/subdir", now)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Navigate to a directory
|
||||
bm.NavigateTo("/test/docs")
|
||||
|
||||
// Verify state was reset
|
||||
if state.CurrentPath != "/test/docs" {
|
||||
t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/test/docs")
|
||||
}
|
||||
|
||||
if state.ScrollOffset != 0 {
|
||||
t.Errorf("ScrollOffset = %f, want 0", state.ScrollOffset)
|
||||
}
|
||||
|
||||
if state.SelectedIndex != -1 {
|
||||
t.Errorf("SelectedIndex = %d, want -1", state.SelectedIndex)
|
||||
}
|
||||
|
||||
// Verify a task was dispatched (should have pending work)
|
||||
// The manager should have queued a BuildIndexTask
|
||||
if !state.Loading {
|
||||
t.Error("Expected Loading to be true after NavigateTo")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNavigateTo_NonExistentDirectory verifies behavior when navigating
|
||||
// to a directory that doesn't exist in the filesystem.
|
||||
func TestNavigateTo_NonExistentDirectory(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Navigate to non-existent directory
|
||||
bm.NavigateTo("/non/existent/path")
|
||||
|
||||
// Should still set the path (error handling happens async)
|
||||
if state.CurrentPath != "/non/existent/path" {
|
||||
t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/non/existent/path")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleResult_BuildIndexSuccess verifies that a successful BuildIndex
|
||||
// result populates the SortIndex and triggers initial page loading.
|
||||
func TestHandleResult_BuildIndexSuccess(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Simulate a successful BuildIndex result
|
||||
result := pool.Result{
|
||||
TaskType: pool.TypeBuildIndex,
|
||||
Success: true,
|
||||
Data: []mock.DirEntry{},
|
||||
}
|
||||
|
||||
bm.HandleResult(result)
|
||||
|
||||
// After successful BuildIndex, Loading should be false
|
||||
if state.Loading {
|
||||
t.Error("Expected Loading to be false after successful BuildIndex result")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleResult_BuildIndexFailure verifies that a failed BuildIndex
|
||||
// result is handled gracefully.
|
||||
func TestHandleResult_BuildIndexFailure(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Simulate a failed BuildIndex result
|
||||
result := pool.Result{
|
||||
TaskType: pool.TypeBuildIndex,
|
||||
Success: false,
|
||||
Error: ErrDirNotFound,
|
||||
}
|
||||
|
||||
bm.HandleResult(result)
|
||||
|
||||
// Should not panic, state should be recoverable
|
||||
if state.CurrentPath != "" {
|
||||
t.Errorf("Expected empty CurrentPath on error, got %q", state.CurrentPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleResult_LoadPagesSuccess verifies that a successful LoadPages
|
||||
// result populates the Pages map.
|
||||
func TestHandleResult_LoadPagesSuccess(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.TotalEntries = 500
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Simulate a successful LoadPages result
|
||||
pageIndices := []int{0, 1}
|
||||
result := pool.Result{
|
||||
TaskType: pool.TypeLoadPages,
|
||||
Success: true,
|
||||
Data: pageIndices,
|
||||
}
|
||||
|
||||
bm.HandleResult(result)
|
||||
|
||||
// Pages should be populated (or at least not cause a panic)
|
||||
// The exact behavior depends on implementation
|
||||
}
|
||||
|
||||
// TestOnScroll_Prefetch verifies that scrolling triggers prefetch for
|
||||
// pages beyond the visible region.
|
||||
func TestOnScroll_Prefetch(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.TotalEntries = 1000
|
||||
state.EntryHeight = 48.0
|
||||
state.VisibleCount = 10
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Set initial scroll position
|
||||
state.ScrollOffset = 0
|
||||
|
||||
// Simulate scrolling down
|
||||
state.ScrollOffset = 5000.0 // ~100 entries down
|
||||
|
||||
bm.OnScroll()
|
||||
|
||||
// Should trigger prefetch (exact behavior depends on implementation)
|
||||
// At minimum, should not panic
|
||||
}
|
||||
|
||||
// TestOnScroll_Eviction verifies that scrolling triggers eviction of
|
||||
// pages far from the visible region.
|
||||
func TestOnScroll_Eviction(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.TotalEntries = 1000
|
||||
state.EntryHeight = 48.0
|
||||
state.VisibleCount = 10
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Pre-populate some pages
|
||||
state.Pages[0] = NewPage(0, make([]Entry, 100))
|
||||
state.Pages[5] = NewPage(5, make([]Entry, 100))
|
||||
state.Pages[10] = NewPage(10, make([]Entry, 100))
|
||||
|
||||
// Scroll far down
|
||||
state.ScrollOffset = 50000.0 // ~1000 entries down
|
||||
|
||||
bm.OnScroll()
|
||||
|
||||
// Pages far from visible region should be evicted
|
||||
// (exact behavior depends on implementation)
|
||||
}
|
||||
|
||||
// TestBrowserManager_StateOwnership verifies that the BrowserManager
|
||||
// does not directly modify state outside of designated methods.
|
||||
func TestBrowserManager_StateOwnership(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify that the manager has a reference to the state
|
||||
// but does not own it (state is owned by the editor)
|
||||
if bm.state != state {
|
||||
t.Error("BrowserManager should have reference to BrowserState")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNavigateTo_ChangesDirectory verifies that navigating to a new
|
||||
// directory clears the previous directory's state.
|
||||
func TestNavigateTo_ChangesDirectory(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
state.CurrentPath = "/old/path"
|
||||
state.TotalEntries = 500
|
||||
state.Pages[0] = NewPage(0, make([]Entry, 100))
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Navigate to new directory
|
||||
bm.NavigateTo("/new/path")
|
||||
|
||||
if state.CurrentPath != "/new/path" {
|
||||
t.Errorf("CurrentPath = %q, want %q", state.CurrentPath, "/new/path")
|
||||
}
|
||||
|
||||
// Pages should be cleared for new directory
|
||||
if len(state.Pages) != 0 {
|
||||
t.Errorf("Expected Pages to be cleared, got %d pages", len(state.Pages))
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleResult_UnknownType verifies that unknown result types
|
||||
// are handled gracefully without panicking.
|
||||
func TestHandleResult_UnknownType(t *testing.T) {
|
||||
state := NewBrowserState()
|
||||
fs := mock.NewFileSystem()
|
||||
wp := pool.NewWorkerPool(1)
|
||||
|
||||
bm, err := NewBrowserManager(state, wp, fs)
|
||||
if err != nil {
|
||||
t.Fatalf("NewBrowserManager failed: %v", err)
|
||||
}
|
||||
|
||||
// Simulate an unknown result type
|
||||
result := pool.Result{
|
||||
TaskType: pool.TypeReadFile, // Not a browser task
|
||||
Success: true,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
bm.HandleResult(result)
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ type ResultEvent struct {
|
|||
// Logic runs the logic goroutine and provides channels for communication.
|
||||
type Logic struct {
|
||||
state *State
|
||||
browserManager *browser.BrowserManager // Add this field
|
||||
configChan chan ConfigUpdate
|
||||
frameChan chan []ui.Element
|
||||
inputChan chan []ui.InputEvent
|
||||
|
|
@ -71,9 +72,11 @@ func NewLogic() *Logic {
|
|||
|
||||
// Set browser initial path to mock root
|
||||
state.Browser.CurrentPath = "/"
|
||||
bm, _ := browser.NewBrowserManager(&state.Browser, wp, mockFS)
|
||||
|
||||
return &Logic{
|
||||
state: state,
|
||||
browserManager: bm, // Add to struct
|
||||
configChan: make(chan ConfigUpdate),
|
||||
frameChan: make(chan []ui.Element),
|
||||
inputChan: make(chan []ui.InputEvent),
|
||||
|
|
@ -168,40 +171,25 @@ func (l *Logic) Run() {
|
|||
|
||||
// handleWorkerResult processes results from the worker pool.
|
||||
func (l *Logic) handleWorkerResult(res pool.Result) {
|
||||
switch res.TaskType {
|
||||
case pool.TypeBuildIndex:
|
||||
l.applyBuildIndexResult(res)
|
||||
case pool.TypeLoadPages:
|
||||
l.applyLoadPagesResult(res)
|
||||
case pool.TypeReadDir:
|
||||
l.applyReadDirResult(res)
|
||||
if res.IsBrowserResult() {
|
||||
l.browserManager.HandleResult(res)
|
||||
}
|
||||
l.frameChan <- l.state.layout()
|
||||
}
|
||||
|
||||
// applyBuildIndexResult applies a completed BuildIndexTask result to browser state.
|
||||
func (l *Logic) applyBuildIndexResult(res pool.Result) {
|
||||
if !res.Success {
|
||||
return
|
||||
}
|
||||
// Convert mock DirEntry results to browser entries
|
||||
if entries, ok := res.Data.([]mock.DirEntry); ok {
|
||||
l.state.Browser.TotalEntries = len(entries)
|
||||
// Build the in-memory index with position maps
|
||||
l.state.Browser.SortIndex = buildBrowserIndex(entries)
|
||||
// Load initial visible pages from the index into the Pages map
|
||||
browser.LoadInitialPages(&l.state.Browser)
|
||||
}
|
||||
// Delegated to browserManager
|
||||
}
|
||||
|
||||
// applyLoadPagesResult applies a completed LoadPagesTask result to browser state.
|
||||
func (l *Logic) applyLoadPagesResult(res pool.Result) {
|
||||
_ = res // TODO: implement page loading from worker results
|
||||
// Delegated to browserManager
|
||||
}
|
||||
|
||||
// applyReadDirResult applies a completed ReadDirTask result to browser state.
|
||||
func (l *Logic) applyReadDirResult(res pool.Result) {
|
||||
_ = res // TODO: implement directory read result handling
|
||||
// Delegated to browserManager
|
||||
}
|
||||
|
||||
// State returns the current state.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user