| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- //
- // IndeedJobBrowserWindowController.swift
- // App for Indeed
- //
- import Cocoa
- import WebKit
- /// Indeed job listing and apply flow in a `WKWebView`, embedded in the dashboard main panel or hosted in a window.
- final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelegate, WKUIDelegate {
- /// When set, a leading **Home** control calls this so the host can hide the embedded browser (same-window UX).
- var onDismissEmbedded: (() -> Void)?
- private let webView: WKWebView = {
- let configuration = WKWebViewConfiguration()
- configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
- return WKWebView(frame: .zero, configuration: configuration)
- }()
- private var pendingURL: URL?
- private let backButton = NSButton()
- private let forwardButton = NSButton()
- private let reloadButton = NSButton()
- private let dismissEmbeddedButton = NSButton(title: "Home", target: nil, action: nil)
- override func loadView() {
- view = NSView(frame: NSRect(x: 0, y: 0, width: 920, height: 720))
- }
- override func viewDidLoad() {
- super.viewDidLoad()
- webView.translatesAutoresizingMaskIntoConstraints = false
- webView.navigationDelegate = self
- webView.uiDelegate = self
- webView.customUserAgent = Self.desktopSafariLikeUserAgent
- configureToolbarButton(backButton, symbolName: "chevron.backward", action: #selector(goBack))
- configureToolbarButton(forwardButton, symbolName: "chevron.forward", action: #selector(goForward))
- configureToolbarButton(reloadButton, symbolName: "arrow.clockwise", action: #selector(reload))
- dismissEmbeddedButton.translatesAutoresizingMaskIntoConstraints = false
- dismissEmbeddedButton.bezelStyle = .rounded
- dismissEmbeddedButton.isBordered = true
- dismissEmbeddedButton.target = self
- dismissEmbeddedButton.action = #selector(dismissEmbedded)
- dismissEmbeddedButton.toolTip = "Return to the previous screen"
- let toolbar = NSView()
- toolbar.translatesAutoresizingMaskIntoConstraints = false
- toolbar.wantsLayer = true
- toolbar.layer?.backgroundColor = NSColor(srgbRed: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1).cgColor
- let barStack: NSStackView
- if onDismissEmbedded != nil {
- barStack = NSStackView(views: [dismissEmbeddedButton, backButton, forwardButton, reloadButton, NSView()])
- } else {
- barStack = NSStackView(views: [backButton, forwardButton, reloadButton, NSView()])
- }
- barStack.orientation = .horizontal
- barStack.spacing = 8
- barStack.alignment = .centerY
- barStack.distribution = .fill
- barStack.translatesAutoresizingMaskIntoConstraints = false
- toolbar.addSubview(barStack)
- view.addSubview(toolbar)
- view.addSubview(webView)
- var layoutConstraints: [NSLayoutConstraint] = [
- toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- toolbar.topAnchor.constraint(equalTo: view.topAnchor),
- toolbar.heightAnchor.constraint(equalToConstant: 48),
- barStack.leadingAnchor.constraint(equalTo: toolbar.leadingAnchor, constant: 12),
- barStack.trailingAnchor.constraint(equalTo: toolbar.trailingAnchor, constant: -12),
- barStack.centerYAnchor.constraint(equalTo: toolbar.centerYAnchor),
- backButton.widthAnchor.constraint(equalToConstant: 32),
- backButton.heightAnchor.constraint(equalToConstant: 28),
- forwardButton.widthAnchor.constraint(equalToConstant: 32),
- forwardButton.heightAnchor.constraint(equalToConstant: 28),
- reloadButton.widthAnchor.constraint(equalToConstant: 32),
- reloadButton.heightAnchor.constraint(equalToConstant: 28),
- webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- webView.topAnchor.constraint(equalTo: toolbar.bottomAnchor),
- webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
- ]
- if onDismissEmbedded != nil {
- layoutConstraints.append(dismissEmbeddedButton.heightAnchor.constraint(equalToConstant: 28))
- }
- NSLayoutConstraint.activate(layoutConstraints)
- updateNavigationButtons()
- if let pendingURL {
- webView.load(URLRequest(url: pendingURL))
- self.pendingURL = nil
- }
- }
- func loadPage(_ url: URL) {
- if isViewLoaded {
- webView.load(URLRequest(url: url))
- } else {
- pendingURL = url
- }
- updateNavigationButtons()
- }
- /// Adds this controller as a child of `parent` and pins `view` to `host` (used by the dashboard main panel).
- func embed(in host: NSView, parent: NSViewController) {
- parent.addChild(self)
- view.translatesAutoresizingMaskIntoConstraints = false
- host.addSubview(view)
- NSLayoutConstraint.activate([
- view.leadingAnchor.constraint(equalTo: host.leadingAnchor),
- view.trailingAnchor.constraint(equalTo: host.trailingAnchor),
- view.topAnchor.constraint(equalTo: host.topAnchor),
- view.bottomAnchor.constraint(equalTo: host.bottomAnchor)
- ])
- }
- private func configureToolbarButton(_ button: NSButton, symbolName: String, action: Selector) {
- button.translatesAutoresizingMaskIntoConstraints = false
- button.bezelStyle = .texturedRounded
- button.isBordered = true
- button.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
- button.imagePosition = .imageOnly
- button.target = self
- button.action = action
- }
- private func updateNavigationButtons() {
- backButton.isEnabled = webView.canGoBack
- forwardButton.isEnabled = webView.canGoForward
- }
- @objc private func goBack() {
- webView.goBack()
- }
- @objc private func goForward() {
- webView.goForward()
- }
- @objc private func reload() {
- webView.reload()
- }
- @objc private func dismissEmbedded() {
- onDismissEmbedded?()
- }
- func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
- updateNavigationButtons()
- }
- func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
- updateNavigationButtons()
- }
- /// Target=_blank / `window.open` without a frame: load in this view so apply flows stay in-app.
- func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
- if navigationAction.targetFrame == nil {
- webView.load(navigationAction.request)
- }
- return nil
- }
- /// Desktop Safari UA helps Indeed serve a full desktop apply experience.
- private static let desktopSafariLikeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
- }
|