SPB Git

spb/zyquo-atlas Public License

The AI-native macOS web browser — every surface, intelligent.

Swift 75.2% JavaScript 22% Shell 2% Makefile 0.9%
5.8 KB · 153 lines swift
Raw Blame History
1//2//  BrowserWindowView.swift3//  Zyquo Atlas4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The browser window root. Composes the chrome — tab strip (top-horizontal OR9//  left-vertical per the live layout), optional bookmarks bar, toolbar, find10//  bar, web content, and the AI sidebar — themed live by ThemeEngine and driven11//  by a per-window WindowState that the app commands target. Hosts the history,12//  bookmarks-manager, downloads, and customization panels.13//1415import SwiftUI1617struct BrowserWindowView: View {18    @EnvironmentObject private var env: AppEnvironment19    @EnvironmentObject private var themeEngine: ThemeEngine20    @StateObject private var tabManager: TabManager21    @StateObject private var windowState = WindowState()2223    private let profile: Profile2425    init(profile: Profile = .defaultProfile) {26        self.profile = profile27        _tabManager = StateObject(wrappedValue: TabManager(profile: profile))28    }2930    private var theme: AtlasTheme { themeEngine.theme }31    private var layout: LayoutSettings { themeEngine.layout }3233    var body: some View {34        Group {35            if layout.tabPosition == .left {36                HStack(spacing: 0) {37                    VerticalTabBarView(tabManager: tabManager, onNewTab: { tabManager.newTab() })38                    mainColumn39                }40            } else {41                VStack(spacing: 0) {42                    TabBarView(tabManager: tabManager)43                    mainColumn44                }45            }46        }47        .background(theme.backgroundColor)48        .frame(minWidth: 900, minHeight: 600)49        .animation(.easeInOut(duration: 0.15), value: windowState.showAISidebar)50        .animation(.easeInOut(duration: 0.18), value: layout.tabPosition)51        .environmentObject(windowState)52        .focusedSceneValue(\.windowState, windowState)53        .onAppear {54            windowState.tabManager = tabManager55            windowState.env = env56            if tabManager.history == nil {57                tabManager.history = profile.isPrivate ? nil : env.history58                tabManager.restoreSessionOrOpenHome()59            }60        }61        .sheet(isPresented: $windowState.showCustomize) {62            CustomizationView().environmentObject(themeEngine)63        }64        .sheet(isPresented: $windowState.showHistory) {65            HistoryView(onOpen: { tabManager.newTab(url: URL(string: $0)) })66                .environmentObject(env).environmentObject(themeEngine)67        }68        .sheet(isPresented: $windowState.showBookmarksManager) {69            BookmarksManagerView(onOpen: { tabManager.newTab(url: URL(string: $0)) })70                .environmentObject(env).environmentObject(themeEngine)71        }72    }7374    // MARK: - Main column (bookmarks bar + toolbar + content + AI sidebar)7576    @ViewBuilder77    private var mainColumn: some View {78        if let tab = tabManager.activeTab {79            VStack(spacing: 0) {80                ToolbarView(81                    tab: tab,82                    isAISidebarOpen: windowState.showAISidebar,83                    onSubmit: { tabManager.loadInActiveTab($0) },84                    onNewTab: { tabManager.newTab() },85                    onToggleAI: { windowState.showAISidebar.toggle() },86                    onCustomize: { windowState.showCustomize = true },87                    downloadCount: tabManager.downloads.items.filter { $0.state == .inProgress }.count,88                    onToggleDownloads: { windowState.showDownloads.toggle() }89                )90                if layout.showBookmarksBar && !profile.isPrivate {91                    BookmarksBarView(onOpen: { tabManager.loadInActiveTab($0) })92                }93                if windowState.showFindBar {94                    FindBarView(tab: tab, onClose: { windowState.showFindBar = false })95                }96                HStack(spacing: 0) {97                    TabContentView(98                        tab: tab,99                        onNavigate: { tabManager.loadInActiveTab($0) },100                        onAsk: { windowState.showAISidebar = true }101                    )102                    if windowState.showAISidebar {103                        AISidebarView(tab: tab, ai: tab.ai, tabManager: tabManager)104                            .transition(.move(edge: .trailing))105                    }106                }107            }108            .overlay(alignment: .topTrailing) {109                if windowState.showDownloads {110                    DownloadsPopover(downloads: tabManager.downloads,111                                     onClose: { windowState.showDownloads = false })112                        .padding(.top, 4).padding(.trailing, 8)113                }114            }115        } else {116            Spacer()117        }118    }119120}121122/// The active tab's content region. Observes the tab so it switches reactively123/// between the start page (blank/home), reader mode, and live web content as the124/// URL changes — and overlays the selection toolbar on web pages.125private struct TabContentView: View {126    @ObservedObject var tab: Tab127    let onNavigate: (String) -> Void128    let onAsk: () -> Void129    @EnvironmentObject private var windowState: WindowState130131    var body: some View {132        if tab.url == nil || tab.url == TabManager.homeURL {133            StartPageView(onNavigate: onNavigate, onAsk: onAsk)134        } else if windowState.showReader {135            ReaderView(tab: tab)136        } else {137            WebContentArea(tab: tab)138                .overlay { SelectionToolbar(tab: tab) }139        }140    }141}142143/// Hosts the active tab's web view. Keyed by tab id so SwiftUI re-mounts the144/// correct WKWebView on a tab switch while background tabs keep theirs alive.145private struct WebContentArea: View {146    @ObservedObject var tab: Tab147148    var body: some View {149        WebView(tab: tab)150            .id(tab.id)151    }152}153