Selaa lähdekoodia

Improve URL field and downloads in browser toolbar

Made-with: Cursor
huzaifahayat12 4 kuukautta sitten
vanhempi
sitoutus
96628bfcc2
1 muutettua tiedostoa jossa 107 lisäystä ja 67 poistoa
  1. 107 67
      google_apps/InAppBrowserWindow.swift

+ 107 - 67
google_apps/InAppBrowserWindow.swift

@@ -40,6 +40,7 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
     private var moreButton: NSButton!
     private var urlField: NSTextField!
     private var isUpdatingURLField = false
+    private var didSubmitURLFromAction = false
     private var isPinnedOnTop = false
     private var isGestureModeEnabled = true
 
@@ -101,30 +102,40 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         toolbarBlur.layer?.cornerRadius = 14
         toolbarBlur.layer?.masksToBounds = true
 
-        func makeIconButton(symbolName: String, accessibility: String, action: Selector) -> NSButton {
+        func makeIconButton(symbolName: String, accessibility: String, action: Selector, fallbackSymbolName: String? = nil) -> NSButton {
             let button = NSButton()
-            button.image = NSImage(
-                systemSymbolName: symbolName,
-                accessibilityDescription: accessibility
-            )
+            let primary = NSImage(systemSymbolName: symbolName, accessibilityDescription: accessibility)
+            let fallback = fallbackSymbolName.flatMap { NSImage(systemSymbolName: $0, accessibilityDescription: accessibility) }
+            button.image = primary ?? fallback
             button.image?.isTemplate = true
             button.isBordered = false
             button.contentTintColor = .white
+            button.imagePosition = .imageOnly
+            button.imageScaling = .scaleProportionallyDown
             button.setButtonType(.momentaryChange)
             button.action = action
             button.setButtonType(.momentaryChange)
 
             // Subtle circular hit target like the screenshot.
             button.wantsLayer = true
-            button.layer?.cornerRadius = 11
+            button.layer?.cornerRadius = 12
             button.layer?.masksToBounds = true
             button.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
             button.layer?.borderColor = NSColor.white.withAlphaComponent(0.10).cgColor
             button.layer?.borderWidth = 1
 
             button.translatesAutoresizingMaskIntoConstraints = false
-            button.widthAnchor.constraint(equalToConstant: 22).isActive = true
-            button.heightAnchor.constraint(equalToConstant: 22).isActive = true
+            button.widthAnchor.constraint(equalToConstant: 26).isActive = true
+            button.heightAnchor.constraint(equalToConstant: 26).isActive = true
+
+            if button.image == nil {
+                // Last-resort fallback if SF Symbols aren’t available for some reason.
+                button.title = "⋮"
+                button.font = NSFont.systemFont(ofSize: 14, weight: .semibold)
+                button.contentTintColor = NSColor.white.withAlphaComponent(0.9)
+            } else {
+                button.title = ""
+            }
 
             button.toolTip = accessibility
             return button
@@ -172,28 +183,54 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
             action: #selector(showLauncher)
         )
 
-        // URL field
+        // URL "pill" wrapper.
+        let urlWrapperView = NSView()
+        urlWrapperView.translatesAutoresizingMaskIntoConstraints = false
+        urlWrapperView.wantsLayer = true
+        urlWrapperView.layer?.cornerRadius = 12
+        urlWrapperView.layer?.masksToBounds = true
+        urlWrapperView.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.07).cgColor
+        urlWrapperView.layer?.borderColor = NSColor.white.withAlphaComponent(0.18).cgColor
+        urlWrapperView.layer?.borderWidth = 1
+
+        // URL field (inside pill)
         urlField = NSTextField()
         urlField.translatesAutoresizingMaskIntoConstraints = false
         urlField.action = #selector(urlFieldDidSubmit)
-        urlField.font = NSFont.systemFont(ofSize: 13, weight: .regular)
+        urlField.placeholderString = "Search or enter website name"
+        urlField.font = NSFont.systemFont(ofSize: 13, weight: .medium)
         urlField.textColor = .white
         urlField.alignment = .left
         urlField.isEditable = true
         urlField.isBordered = false
         urlField.drawsBackground = false
-        urlField.wantsLayer = true
-        urlField.layer?.backgroundColor = NSColor.clear.cgColor
-
-        // URL "pill" wrapper.
-        let urlWrapperView = NSView()
-        urlWrapperView.translatesAutoresizingMaskIntoConstraints = false
-        urlWrapperView.wantsLayer = true
-        urlWrapperView.layer?.cornerRadius = 10
-        urlWrapperView.layer?.masksToBounds = true
-        urlWrapperView.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
-        urlWrapperView.layer?.borderColor = NSColor.white.withAlphaComponent(0.12).cgColor
-        urlWrapperView.layer?.borderWidth = 1
+        urlField.focusRingType = .none
+
+        // Optional "lock" glyph like browsers (purely visual).
+        let lockImageView = NSImageView()
+        lockImageView.translatesAutoresizingMaskIntoConstraints = false
+        lockImageView.image = NSImage(systemSymbolName: "lock.fill", accessibilityDescription: "Secure")
+        lockImageView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
+        lockImageView.contentTintColor = NSColor.white.withAlphaComponent(0.7)
+        lockImageView.imageScaling = .scaleProportionallyDown
+
+        let urlInnerStack = NSStackView(views: [lockImageView, urlField])
+        urlInnerStack.orientation = .horizontal
+        urlInnerStack.alignment = .centerY
+        urlInnerStack.spacing = 8
+        urlInnerStack.translatesAutoresizingMaskIntoConstraints = false
+
+        urlWrapperView.addSubview(urlInnerStack)
+        NSLayoutConstraint.activate([
+            lockImageView.widthAnchor.constraint(equalToConstant: 14),
+            lockImageView.heightAnchor.constraint(equalToConstant: 14),
+
+            urlInnerStack.leadingAnchor.constraint(equalTo: urlWrapperView.leadingAnchor, constant: 12),
+            urlInnerStack.trailingAnchor.constraint(equalTo: urlWrapperView.trailingAnchor, constant: -12),
+            urlInnerStack.topAnchor.constraint(equalTo: urlWrapperView.topAnchor, constant: 6),
+            urlInnerStack.bottomAnchor.constraint(equalTo: urlWrapperView.bottomAnchor, constant: -6),
+            urlWrapperView.heightAnchor.constraint(equalToConstant: 30),
+        ])
 
         // Open in external browser
         openInBrowserButton = makeIconButton(
@@ -209,18 +246,19 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
             action: #selector(toggleGestureMode)
         )
 
-        // Download (save as PDF)
+        // Downloads (open Downloads folder)
         downloadButton = makeIconButton(
             symbolName: "arrow.down.circle",
-            accessibility: "Download PDF",
-            action: #selector(downloadPDF)
+            accessibility: "Downloads",
+            action: #selector(openDownloads)
         )
 
         // More menu
         moreButton = makeIconButton(
             symbolName: "ellipsis.vertical",
             accessibility: "More",
-            action: #selector(showMoreMenu)
+            action: #selector(showMoreMenu),
+            fallbackSymbolName: "ellipsis"
         )
 
         toolbarView.addArrangedSubview(backButton)
@@ -239,14 +277,7 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         urlWrapperView.setContentHuggingPriority(.defaultLow, for: .horizontal)
         urlWrapperView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
 
-        urlWrapperView.addSubview(urlField)
-        NSLayoutConstraint.activate([
-            urlField.leadingAnchor.constraint(equalTo: urlWrapperView.leadingAnchor, constant: 12),
-            urlField.trailingAnchor.constraint(equalTo: urlWrapperView.trailingAnchor, constant: -12),
-            urlField.topAnchor.constraint(equalTo: urlWrapperView.topAnchor, constant: 6),
-            urlField.bottomAnchor.constraint(equalTo: urlWrapperView.bottomAnchor, constant: -6),
-            urlWrapperView.heightAnchor.constraint(equalToConstant: 28),
-        ])
+        // (URL layout constraints are defined via urlInnerStack above)
 
         // Web view below toolbar.
         webView.translatesAutoresizingMaskIntoConstraints = false
@@ -302,6 +333,7 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         // Initialize toolbar state.
         setToggledStyle(pinButton, isOn: isPinnedOnTop)
         setToggledStyle(handButton, isOn: isGestureModeEnabled)
+        urlField.stringValue = initialURL.absoluteString
         updateToolbarState()
     }
 
@@ -372,39 +404,12 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         }
     }
 
-    @objc private func downloadPDF() {
-        guard let url = webView.url else { return }
-
-        let host = (url.host?.isEmpty == false) ? (url.host ?? "page") : "page"
-        let dateFormatter = DateFormatter()
-        dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
-        let fileName = "\(host)_\(dateFormatter.string(from: Date())).pdf"
-
+    @objc private func openDownloads() {
         guard let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else {
-            showAlert(title: "Download Failed", message: "Couldn’t find your Downloads folder.")
+            showAlert(title: "Unavailable", message: "Couldn’t find your Downloads folder.")
             return
         }
-
-        let targetURL = downloads.appendingPathComponent(fileName)
-
-        if #available(macOS 11.0, *) {
-            webView.createPDF(configuration: WKPDFConfiguration()) { [weak self] result in
-                guard let self else { return }
-                switch result {
-                case .success(let data):
-                    do {
-                        try data.write(to: targetURL, options: .atomic)
-                        self.showAlert(title: "Downloaded", message: "Saved PDF to Downloads:\n\(fileName)")
-                    } catch {
-                        self.showAlert(title: "Download Failed", message: "Couldn’t save the PDF.")
-                    }
-                case .failure:
-                    self.showAlert(title: "Download Failed", message: "Couldn’t generate a PDF for this page.")
-                }
-            }
-        } else {
-            showAlert(title: "Unavailable", message: "PDF download requires macOS 11 or newer.")
-        }
+        NSWorkspace.shared.open(downloads)
     }
 
     @objc private func showMoreMenu() {
@@ -424,6 +429,10 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
         home.keyEquivalentModifierMask = [.command]
         menu.addItem(home)
 
+        let downloads = NSMenuItem(title: "Open Downloads", action: #selector(openDownloads), keyEquivalent: "d")
+        downloads.keyEquivalentModifierMask = [.command]
+        menu.addItem(downloads)
+
         let openExternal = NSMenuItem(title: "Open in Default Browser", action: #selector(openInBrowser), keyEquivalent: "o")
         openExternal.keyEquivalentModifierMask = [.command]
         menu.addItem(openExternal)
@@ -451,6 +460,7 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
     }
 
     @objc private func urlFieldDidSubmit() {
+        didSubmitURLFromAction = true
         loadFromURLField()
     }
 
@@ -470,10 +480,12 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
             return
         }
 
-        // Avoid the URL field update triggering other edits while we load.
+        // Keep normalized URL visible immediately after submit.
         isUpdatingURLField = true
-        webView.load(URLRequest(url: url))
+        urlField.stringValue = normalized
         isUpdatingURLField = false
+
+        webView.load(URLRequest(url: url))
     }
 
     private func revertURLFieldToCurrentWebURL() {
@@ -489,12 +501,24 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
 
     private func setURLFieldToCurrentWebURL() {
         guard let url = webView.url?.absoluteString else { return }
-        guard !isURLFieldBeingEdited else { return }
         guard !isUpdatingURLField else { return }
 
+        // Avoid overriding manual typing unless this update follows a submit.
+        // However, right after navigation starts the text field can still be
+        // considered "editing" (first responder), which would otherwise keep
+        // the URL pill blank/stale. If it's empty, we should populate it.
+        if isURLFieldBeingEdited && !didSubmitURLFromAction {
+            if urlField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+                // proceed
+            } else {
+                return
+            }
+        }
+
         isUpdatingURLField = true
         urlField.stringValue = url
         isUpdatingURLField = false
+        didSubmitURLFromAction = false
     }
 
     private func updateToolbarState() {
@@ -541,15 +565,25 @@ extension InAppBrowserWindowController: WKNavigationDelegate {
         updateToolbarState()
     }
 
+    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+        updateToolbarState()
+    }
+
+    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
+        updateToolbarState()
+    }
+
     func webView(
         _ webView: WKWebView,
         didFailProvisionalNavigation navigation: WKNavigation!,
         withError error: Error
     ) {
+        updateToolbarState()
         showLoadError(error)
     }
 
     func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
+        updateToolbarState()
         showLoadError(error)
     }
 
@@ -572,6 +606,12 @@ extension InAppBrowserWindowController: NSTextFieldDelegate {
     func controlTextDidEndEditing(_ obj: Notification) {
         guard obj.object as? NSTextField === urlField else { return }
         guard !isUpdatingURLField else { return }
+        // Return key triggers both action and end-editing callbacks.
+        // Prevent duplicate loads so history/back-forward stays stable.
+        if didSubmitURLFromAction {
+            didSubmitURLFromAction = false
+            return
+        }
         loadFromURLField()
     }
 }