|
|
@@ -53,6 +53,10 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
53
|
53
|
/// Flipped so short result lists stay visually under the search bar instead of leaving a gap above the cards.
|
|
54
|
54
|
private let jobListingsContainer = JobListingsDocumentView()
|
|
55
|
55
|
private let jobListingsStack = NSStackView()
|
|
|
56
|
+ /// Shown when a sidebar item other than Home is selected.
|
|
|
57
|
+ private let nonHomeHost = NSView()
|
|
|
58
|
+ private let nonHomeTitleLabel = NSTextField(labelWithString: "")
|
|
|
59
|
+ private let nonHomeSubtitleLabel = NSTextField(wrappingLabelWithString: "")
|
|
56
|
60
|
|
|
57
|
61
|
private var currentSidebarItems: [SidebarItem] = []
|
|
58
|
62
|
private var selectedSidebarIndex: Int = 0
|
|
|
@@ -87,6 +91,7 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
87
|
91
|
configureSidebar()
|
|
88
|
92
|
catalogJobListings = data.jobListings
|
|
89
|
93
|
configureJobListings([], noResultsForQuery: nil)
|
|
|
94
|
+ updateMainContentVisibility()
|
|
90
|
95
|
}
|
|
91
|
96
|
|
|
92
|
97
|
private func setupLayout() {
|
|
|
@@ -126,6 +131,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
126
|
131
|
mainHost.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
127
|
132
|
|
|
128
|
133
|
mainHost.addSubview(mainOverlay)
|
|
|
134
|
+ configureNonHomePlaceholder()
|
|
|
135
|
+ mainHost.addSubview(nonHomeHost)
|
|
129
|
136
|
|
|
130
|
137
|
mainOverlay.orientation = .vertical
|
|
131
|
138
|
mainOverlay.spacing = 0
|
|
|
@@ -219,6 +226,11 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
219
|
226
|
mainOverlay.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
220
|
227
|
mainOverlay.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
221
|
228
|
|
|
|
229
|
+ nonHomeHost.leadingAnchor.constraint(equalTo: mainHost.leadingAnchor),
|
|
|
230
|
+ nonHomeHost.trailingAnchor.constraint(equalTo: mainHost.trailingAnchor),
|
|
|
231
|
+ nonHomeHost.topAnchor.constraint(equalTo: mainHost.topAnchor),
|
|
|
232
|
+ nonHomeHost.bottomAnchor.constraint(equalTo: mainHost.bottomAnchor, constant: -24),
|
|
|
233
|
+
|
|
222
|
234
|
searchBarShadowHost.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
223
|
235
|
jobListingsScrollView.widthAnchor.constraint(equalTo: mainOverlay.widthAnchor, multiplier: 0.92),
|
|
224
|
236
|
|
|
|
@@ -485,6 +497,62 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
485
|
497
|
)
|
|
486
|
498
|
}
|
|
487
|
499
|
|
|
|
500
|
+ private func configureNonHomePlaceholder() {
|
|
|
501
|
+ nonHomeHost.translatesAutoresizingMaskIntoConstraints = false
|
|
|
502
|
+ nonHomeHost.wantsLayer = true
|
|
|
503
|
+ nonHomeHost.layer?.backgroundColor = Theme.mainHostBackground.cgColor
|
|
|
504
|
+ nonHomeHost.isHidden = true
|
|
|
505
|
+
|
|
|
506
|
+ nonHomeTitleLabel.font = .systemFont(ofSize: 22, weight: .bold)
|
|
|
507
|
+ nonHomeTitleLabel.textColor = Theme.primaryText
|
|
|
508
|
+ nonHomeTitleLabel.alignment = .center
|
|
|
509
|
+ nonHomeTitleLabel.maximumNumberOfLines = 1
|
|
|
510
|
+
|
|
|
511
|
+ nonHomeSubtitleLabel.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
512
|
+ nonHomeSubtitleLabel.textColor = Theme.secondaryText
|
|
|
513
|
+ nonHomeSubtitleLabel.alignment = .center
|
|
|
514
|
+ nonHomeSubtitleLabel.maximumNumberOfLines = 0
|
|
|
515
|
+ nonHomeSubtitleLabel.stringValue = "This area is not available in the preview build. Use Home to search jobs."
|
|
|
516
|
+
|
|
|
517
|
+ let stack = NSStackView(views: [nonHomeTitleLabel, nonHomeSubtitleLabel])
|
|
|
518
|
+ stack.orientation = .vertical
|
|
|
519
|
+ stack.spacing = 10
|
|
|
520
|
+ stack.alignment = .centerX
|
|
|
521
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
522
|
+ nonHomeHost.addSubview(stack)
|
|
|
523
|
+ NSLayoutConstraint.activate([
|
|
|
524
|
+ stack.centerXAnchor.constraint(equalTo: nonHomeHost.centerXAnchor),
|
|
|
525
|
+ stack.centerYAnchor.constraint(equalTo: nonHomeHost.centerYAnchor),
|
|
|
526
|
+ stack.leadingAnchor.constraint(greaterThanOrEqualTo: nonHomeHost.leadingAnchor, constant: 32),
|
|
|
527
|
+ stack.trailingAnchor.constraint(lessThanOrEqualTo: nonHomeHost.trailingAnchor, constant: -32),
|
|
|
528
|
+ nonHomeSubtitleLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 420)
|
|
|
529
|
+ ])
|
|
|
530
|
+ }
|
|
|
531
|
+
|
|
|
532
|
+ private func isHomeSidebarIndex(_ index: Int) -> Bool {
|
|
|
533
|
+ guard index >= 0, index < currentSidebarItems.count else { return false }
|
|
|
534
|
+ return currentSidebarItems[index].title == "Home"
|
|
|
535
|
+ }
|
|
|
536
|
+
|
|
|
537
|
+ private func updateMainContentVisibility() {
|
|
|
538
|
+ let home = isHomeSidebarIndex(selectedSidebarIndex)
|
|
|
539
|
+ mainOverlay.isHidden = !home
|
|
|
540
|
+ nonHomeHost.isHidden = home
|
|
|
541
|
+ if !home, selectedSidebarIndex < currentSidebarItems.count {
|
|
|
542
|
+ nonHomeTitleLabel.stringValue = currentSidebarItems[selectedSidebarIndex].title
|
|
|
543
|
+ }
|
|
|
544
|
+ }
|
|
|
545
|
+
|
|
|
546
|
+ /// Restores the main job-search experience: cleared query and no listings until the user searches again.
|
|
|
547
|
+ private func applyHomeState() {
|
|
|
548
|
+ jobKeywordsField.stringValue = ""
|
|
|
549
|
+ configureJobListings([], noResultsForQuery: nil)
|
|
|
550
|
+ window?.makeFirstResponder(nil)
|
|
|
551
|
+ if let doc = jobListingsScrollView.documentView {
|
|
|
552
|
+ doc.scroll(NSPoint(x: 0, y: 0))
|
|
|
553
|
+ }
|
|
|
554
|
+ }
|
|
|
555
|
+
|
|
488
|
556
|
private func updateSearchBarShadowPath() {
|
|
489
|
557
|
guard searchBarShadowHost.bounds.width > 0, searchBarShadowHost.bounds.height > 0 else { return }
|
|
490
|
558
|
let r = searchBarShadowHost.bounds
|
|
|
@@ -726,9 +794,20 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
726
|
794
|
}
|
|
727
|
795
|
|
|
728
|
796
|
private func selectSidebarItem(at index: Int) {
|
|
729
|
|
- guard index >= 0, index < currentSidebarItems.count, index != selectedSidebarIndex else { return }
|
|
|
797
|
+ guard index >= 0, index < currentSidebarItems.count else { return }
|
|
|
798
|
+ let selectingHome = isHomeSidebarIndex(index)
|
|
|
799
|
+ if index == selectedSidebarIndex {
|
|
|
800
|
+ if selectingHome {
|
|
|
801
|
+ applyHomeState()
|
|
|
802
|
+ }
|
|
|
803
|
+ return
|
|
|
804
|
+ }
|
|
730
|
805
|
selectedSidebarIndex = index
|
|
731
|
806
|
configureSidebar()
|
|
|
807
|
+ updateMainContentVisibility()
|
|
|
808
|
+ if selectingHome {
|
|
|
809
|
+ applyHomeState()
|
|
|
810
|
+ }
|
|
732
|
811
|
}
|
|
733
|
812
|
|
|
734
|
813
|
}
|