|
@@ -106,6 +106,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
private let chatStack = NSStackView()
|
|
private let chatStack = NSStackView()
|
|
|
/// Shown when a sidebar item other than Home is selected.
|
|
/// Shown when a sidebar item other than Home is selected.
|
|
|
private let nonHomeHost = NSView()
|
|
private let nonHomeHost = NSView()
|
|
|
|
|
+ /// Full-bleed Indeed apply / listing web view inside the main panel (same window as the dashboard).
|
|
|
|
|
+ private let indeedJobBrowserHost = NSView()
|
|
|
private let nonHomeGenericContainer = NSView()
|
|
private let nonHomeGenericContainer = NSView()
|
|
|
private let nonHomeTitleLabel = NSTextField(labelWithString: "")
|
|
private let nonHomeTitleLabel = NSTextField(labelWithString: "")
|
|
|
private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
|
|
private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
|
|
@@ -137,6 +139,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
private var chatThinkingRowHost: NSView?
|
|
private var chatThinkingRowHost: NSView?
|
|
|
private let jobSearchService = OpenAIJobSearchService()
|
|
private let jobSearchService = OpenAIJobSearchService()
|
|
|
private var premiumPlansWindowController: PremiumPlansWindowController?
|
|
private var premiumPlansWindowController: PremiumPlansWindowController?
|
|
|
|
|
+ private var indeedJobBrowserViewController: IndeedJobBrowserViewController?
|
|
|
|
|
+ private var isIndeedJobBrowserPresented = false
|
|
|
private weak var sidebarUpgradeCard: NSView?
|
|
private weak var sidebarUpgradeCard: NSView?
|
|
|
private weak var sidebarUpgradeHeadline: NSTextField?
|
|
private weak var sidebarUpgradeHeadline: NSTextField?
|
|
|
private weak var sidebarUpgradeDescription: NSTextField?
|
|
private weak var sidebarUpgradeDescription: NSTextField?
|
|
@@ -187,6 +191,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func render(_ data: DashboardData) {
|
|
func render(_ data: DashboardData) {
|
|
|
|
|
+ dismissIndeedJobBrowserEmbedded()
|
|
|
greetingLabel.stringValue = "Welcome"
|
|
greetingLabel.stringValue = "Welcome"
|
|
|
subtitleLabel.stringValue = data.subtitle
|
|
subtitleLabel.stringValue = data.subtitle
|
|
|
currentSidebarItems = data.sidebarItems
|
|
currentSidebarItems = data.sidebarItems
|
|
@@ -241,6 +246,12 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
configureNonHomePlaceholder()
|
|
configureNonHomePlaceholder()
|
|
|
mainHost.addSubview(nonHomeHost)
|
|
mainHost.addSubview(nonHomeHost)
|
|
|
|
|
|
|
|
|
|
+ indeedJobBrowserHost.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ indeedJobBrowserHost.wantsLayer = true
|
|
|
|
|
+ indeedJobBrowserHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
|
|
+ indeedJobBrowserHost.isHidden = true
|
|
|
|
|
+ mainHost.addSubview(indeedJobBrowserHost)
|
|
|
|
|
+
|
|
|
mainOverlay.orientation = .vertical
|
|
mainOverlay.orientation = .vertical
|
|
|
mainOverlay.spacing = 0
|
|
mainOverlay.spacing = 0
|
|
|
mainOverlay.alignment = .centerX
|
|
mainOverlay.alignment = .centerX
|
|
@@ -347,6 +358,11 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
nonHomeHost.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
nonHomeHost.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
|
nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
|
|
|
|
|
|
|
|
+ indeedJobBrowserHost.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
|
|
|
|
|
+ indeedJobBrowserHost.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
|
|
|
|
|
+ indeedJobBrowserHost.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
|
|
|
+ indeedJobBrowserHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
|
|
|
+
|
|
|
searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
|
featureCardsRow.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
featureCardsRow.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
|
chatScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
chatScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
@@ -824,14 +840,49 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
|
|
|
|
|
@objc private func didTapJobApply(_ sender: NSButton) {
|
|
@objc private func didTapJobApply(_ sender: NSButton) {
|
|
|
guard let job = (sender as? JobPayloadButton)?.jobPayload else { return }
|
|
guard let job = (sender as? JobPayloadButton)?.jobPayload else { return }
|
|
|
- if let rawURL = job.url, let url = URL(string: rawURL), !rawURL.isEmpty {
|
|
|
|
|
- NSWorkspace.shared.open(url)
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ let url: URL
|
|
|
|
|
+ if let rawURL = job.url, let resolved = URL(string: rawURL), !rawURL.isEmpty {
|
|
|
|
|
+ url = resolved
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let allowed = CharacterSet.urlQueryAllowed
|
|
|
|
|
+ let q = job.title.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
|
|
|
|
|
+ guard let fallback = URL(string: "https://www.indeed.com/jobs?q=\(q)") else { return }
|
|
|
|
|
+ url = fallback
|
|
|
|
|
+ }
|
|
|
|
|
+ presentIndeedJobBrowser(url: url)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func presentIndeedJobBrowser(url: URL) {
|
|
|
|
|
+ guard let parentVC = hostingViewController else { return }
|
|
|
|
|
+
|
|
|
|
|
+ if indeedJobBrowserViewController == nil {
|
|
|
|
|
+ let vc = IndeedJobBrowserViewController()
|
|
|
|
|
+ vc.onDismissEmbedded = { [weak self] in
|
|
|
|
|
+ self?.dismissIndeedJobBrowserEmbedded()
|
|
|
|
|
+ }
|
|
|
|
|
+ vc.embed(in: indeedJobBrowserHost, parent: parentVC)
|
|
|
|
|
+ indeedJobBrowserViewController = vc
|
|
|
|
|
+ }
|
|
|
|
|
+ indeedJobBrowserViewController?.loadPage(url)
|
|
|
|
|
+ isIndeedJobBrowserPresented = true
|
|
|
|
|
+ updateMainContentVisibility()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func dismissIndeedJobBrowserEmbedded() {
|
|
|
|
|
+ guard isIndeedJobBrowserPresented else { return }
|
|
|
|
|
+ isIndeedJobBrowserPresented = false
|
|
|
|
|
+ updateMainContentVisibility()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private var hostingViewController: NSViewController? {
|
|
|
|
|
+ var responder: NSResponder? = self
|
|
|
|
|
+ while let current = responder {
|
|
|
|
|
+ if let viewController = current as? NSViewController {
|
|
|
|
|
+ return viewController
|
|
|
|
|
+ }
|
|
|
|
|
+ responder = current.nextResponder
|
|
|
}
|
|
}
|
|
|
- let allowed = CharacterSet.urlQueryAllowed
|
|
|
|
|
- let q = job.title.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
|
|
|
|
|
- guard let url = URL(string: "https://www.indeed.com/jobs?q=\(q)") else { return }
|
|
|
|
|
- NSWorkspace.shared.open(url)
|
|
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@objc private func didTapJobSaved(_ sender: NSButton) {
|
|
@objc private func didTapJobSaved(_ sender: NSButton) {
|
|
@@ -1448,6 +1499,13 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func updateMainContentVisibility() {
|
|
private func updateMainContentVisibility() {
|
|
|
|
|
+ if isIndeedJobBrowserPresented {
|
|
|
|
|
+ mainOverlay.isHidden = true
|
|
|
|
|
+ nonHomeHost.isHidden = true
|
|
|
|
|
+ indeedJobBrowserHost.isHidden = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ indeedJobBrowserHost.isHidden = true
|
|
|
let home = isHomeSidebarIndex(selectedSidebarIndex)
|
|
let home = isHomeSidebarIndex(selectedSidebarIndex)
|
|
|
let savedJobs = isSavedJobsSidebarIndex(selectedSidebarIndex)
|
|
let savedJobs = isSavedJobsSidebarIndex(selectedSidebarIndex)
|
|
|
let settings = isSettingsSidebarIndex(selectedSidebarIndex)
|
|
let settings = isSettingsSidebarIndex(selectedSidebarIndex)
|
|
@@ -2208,6 +2266,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func selectSidebarItem(at index: Int) {
|
|
private func selectSidebarItem(at index: Int) {
|
|
|
|
|
+ dismissIndeedJobBrowserEmbedded()
|
|
|
guard index >= 0, index < currentSidebarItems.count else { return }
|
|
guard index >= 0, index < currentSidebarItems.count else { return }
|
|
|
let selectingHome = isHomeSidebarIndex(index)
|
|
let selectingHome = isHomeSidebarIndex(index)
|
|
|
if index == selectedSidebarIndex {
|
|
if index == selectedSidebarIndex {
|