import SwiftUI import WaterfallGrid struct PostGridView: View { @EnvironmentObject var settings: SettingsManager @ObservedObject var manager: BooruManager @State private var isSearchHistoryPresented = false @Binding var selectedTab: Int let isActive: Bool @Binding var selectedPost: (post: BooruPost?, manager: BooruManager?) @Environment(\.isSearching) private var isSearching var filteredPosts: [BooruPost] { manager.posts .filter { settings.displayRatings.contains($0.rating) } } var body: some View { ScrollViewReader { _ in ScrollView { if filteredPosts.isEmpty { ProgressView() .padding() } WaterfallGrid(filteredPosts, id: \.id) { post in waterfallGridContent(post: post) } .gridStyle(columns: settings.thumbnailGridColumns) .padding(8) } #if os(macOS) .searchable(text: $manager.searchText, prompt: "Tags") .searchSuggestions { if settings.searchSuggestionsMode != .disabled { SearchSuggestionsView( items: searchSuggestionsItems(), searchText: $manager.searchText ) } } .onSubmit(of: .search) { manager.performSearch(settings: settings) } .navigationDestination(for: BooruPost.self) { post in PostDetailsView(selectedPost: $selectedPost) } .onChange(of: manager.searchText) { _, _ in if manager.searchText.isEmpty, !isSearching { Task { manager.performSearch() } } } #endif .toolbar { if isActive { #if os(macOS) ToolbarItem { Button(action: { Task { await manager.fetchPosts(page: 1, tags: manager.tags, replace: true) } }) { Label("Refresh", systemImage: "arrow.clockwise") } .disabled(manager.isLoading) } #endif #if os(macOS) ToolbarItem { Button(action: { Task { manager.loadNextPage() } }) { Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath") } .disabled(manager.isLoading) } #else ToolbarItem(placement: .bottomBar) { Button(action: { Task { manager.loadNextPage() } }) { Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath") } .disabled(manager.isLoading) } ToolbarItem(placement: .bottomBar) { Button(action: { Task { isSearchHistoryPresented.toggle() } }) { Label("Search History", systemImage: "clock.arrow.circlepath") } } if manager.isLoading { ToolbarItem { ProgressView() } } #endif if !manager.tags.isEmpty { #if os(macOS) ToolbarItem { PostGridBookmarkButtonView(manager: manager) } #else ToolbarItem(placement: .bottomBar) { PostGridBookmarkButtonView(manager: manager) } #endif } } } .navigationTitle("Posts") #if !os(macOS) .navigationBarTitleDisplayMode(.inline) #endif .refreshable { await manager.fetchPosts(page: 1, tags: manager.tags, replace: true) } .scrollDisabled(manager.isLoading) .sheet(isPresented: $isSearchHistoryPresented) { PostGridSearchHistoryView( selectedTab: $selectedTab, isPresented: $isSearchHistoryPresented, manager: manager ) .frame(minHeight: 250) } } } private func waterfallGridContent(post: BooruPost) -> some View { Button { if isActive { selectedPost.post = post selectedPost.manager = manager } } label: { PostGridThumbnailView(post: post, posts: filteredPosts, manager: manager) } .buttonStyle(PlainButtonStyle()) .contextMenu { Button(action: { selectedPost.post = post selectedPost.manager = manager }) { Label("Select Post", systemImage: "arrow.right.circle") } } } private func searchSuggestionsItems() -> [Either] { let items: [Either] switch settings.searchSuggestionsMode { case .tags: items = manager.allTags .map { Either.left($0) } case .history: items = settings.searchHistory .map { Either.right($0) } case .disabled: items = [] } return items } }