spb/zyquo-atlas Public License
The AI-native macOS web browser — every surface, intelligent.
Swift 75.2%
JavaScript 22%
Shell 2%
Makefile 0.9%
1//2// BookmarksManagerView.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Favorites manager (⌥⌘B): searchable list of bookmarks with edit (title/notes/9// tags/on-bar), delete, and HTML import/export. Folders and drag-organization10// build on BookmarksService.11//1213import SwiftUI14import AppKit1516struct BookmarksManagerView: View {17 let onOpen: (String) -> Void18 @EnvironmentObject private var env: AppEnvironment19 @EnvironmentObject private var themeEngine: ThemeEngine20 @Environment(\.dismiss) private var dismiss21 private var theme: AtlasTheme { themeEngine.theme }2223 @State private var query = ""24 @State private var editing: Bookmark?2526 var body: some View {27 VStack(spacing: 0) {28 header29 Divider().overlay(theme.border)30 list31 }32 .frame(width: 640, height: 600)33 .background(theme.backgroundColor)34 .sheet(item: $editing) { bm in editor(bm) }35 }3637 private var header: some View {38 HStack {39 Label("Favorites", systemImage: "star").font(ZyquoFont.title).foregroundStyle(theme.textPrimary)40 Spacer()41 HStack(spacing: ZyquoSpacing.xs) {42 Image(systemName: "magnifyingglass").font(.system(size: 11)).foregroundStyle(theme.textTertiary)43 TextField("Search favorites", text: $query).textFieldStyle(.plain).font(ZyquoFont.control)44 .foregroundStyle(theme.textPrimary).frame(width: 180)45 }46 .padding(.horizontal, ZyquoSpacing.sm).frame(height: 28)47 .background(RoundedRectangle(cornerRadius: ZyquoRadius.small).fill(theme.surfaceSecondary))48 Button("Import") { importHTML() }.foregroundStyle(theme.accent)49 Button("Export") { exportHTML() }.foregroundStyle(theme.accent)50 Button("Done") { dismiss() }.foregroundStyle(theme.accent)51 }52 .padding(ZyquoSpacing.md).background(theme.surface)53 }5455 private var list: some View {56 ScrollView {57 LazyVStack(spacing: 0) {58 ForEach(env.bookmarks.search(query)) { bm in59 row(bm)60 Divider().overlay(theme.border)61 }62 if env.bookmarks.bookmarks.isEmpty {63 Text("No favorites yet — bookmark a page with ⌘D")64 .font(ZyquoFont.caption).foregroundStyle(theme.textTertiary)65 .frame(maxWidth: .infinity).padding(ZyquoSpacing.xxl)66 }67 }68 }69 }7071 private func row(_ bm: Bookmark) -> some View {72 HStack(spacing: ZyquoSpacing.sm) {73 Image(systemName: bm.onBar ? "star.fill" : "globe")74 .font(.system(size: 11)).foregroundStyle(bm.onBar ? theme.accent : theme.textTertiary)75 VStack(alignment: .leading, spacing: 1) {76 Text(bm.title).font(ZyquoFont.body(size: 12.5)).lineLimit(1).foregroundStyle(theme.textPrimary)77 Text(bm.url).font(ZyquoFont.caption).lineLimit(1).foregroundStyle(theme.textTertiary)78 }79 Spacer()80 if !bm.tags.isEmpty {81 Text(bm.tags.joined(separator: ", ")).font(ZyquoFont.caption).foregroundStyle(theme.accentIndigo)82 }83 Button { editing = bm } label: { Image(systemName: "pencil") }84 .buttonStyle(.plain).foregroundStyle(theme.textSecondary)85 Button { env.bookmarks.delete(bm.id) } label: { Image(systemName: "trash") }86 .buttonStyle(.plain).foregroundStyle(theme.textTertiary)87 }88 .padding(.horizontal, ZyquoSpacing.md).padding(.vertical, ZyquoSpacing.xs)89 .contentShape(Rectangle())90 .onTapGesture { onOpen(bm.url); dismiss() }91 }9293 private func editor(_ bm: Bookmark) -> some View {94 var draft = bm95 return VStack(alignment: .leading, spacing: ZyquoSpacing.sm) {96 Text("Edit Favorite").font(ZyquoFont.bodyEmphasis()).foregroundStyle(theme.textPrimary)97 EditorField(label: "Title", text: draft.title) { draft.title = $0 }98 EditorField(label: "URL", text: draft.url) { draft.url = $0 }99 EditorField(label: "Tags (comma-separated)", text: draft.tags.joined(separator: ", ")) {100 draft.tags = $0.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }101 }102 EditorField(label: "Notes", text: draft.notes) { draft.notes = $0 }103 Toggle("Show on bookmarks bar", isOn: Binding(get: { draft.onBar }, set: { draft.onBar = $0 }))104 .toggleStyle(.switch).font(ZyquoFont.body()).foregroundStyle(theme.textPrimary)105 HStack {106 Spacer()107 Button("Cancel") { editing = nil }108 Button("Save") { env.bookmarks.update(draft); editing = nil }.keyboardShortcut(.defaultAction)109 }110 }111 .padding(ZyquoSpacing.lg).frame(width: 420).background(theme.surface)112 }113114 private func importHTML() {115 let p = NSOpenPanel(); p.allowedContentTypes = [.html]; p.allowsMultipleSelection = false116 if p.runModal() == .OK, let url = p.url { _ = try? env.bookmarks.importHTML(from: url) }117 }118 private func exportHTML() {119 let p = NSSavePanel(); p.allowedContentTypes = [.html]; p.nameFieldStringValue = "zyquo-atlas-bookmarks.html"120 if p.runModal() == .OK, let url = p.url { try? env.bookmarks.exportHTML(to: url) }121 }122}123124/// A labeled single-line editor row that reports edits via a callback.125private struct EditorField: View {126 let label: String127 @State var text: String128 let onChange: (String) -> Void129 @EnvironmentObject private var themeEngine: ThemeEngine130131 var body: some View {132 VStack(alignment: .leading, spacing: 2) {133 Text(label).font(ZyquoFont.caption).foregroundStyle(themeEngine.theme.textTertiary)134 TextField("", text: $text)135 .textFieldStyle(.roundedBorder)136 .onChange(of: text) { onChange($0) }137 }138 }139}140