Show no-selection or multiple-selection text in the detail view when appropriate.

This commit is contained in:
Brent Simmons 2018-02-18 21:49:46 -08:00
parent 2bb3d5c6ca
commit 1ba2306b9c
4 changed files with 147 additions and 70 deletions

View File

@ -2,7 +2,9 @@
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14092"/>
<capability name="box content view" minToolsVersion="7.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<capability name="system font weights other than Regular or Bold" minToolsVersion="7.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Window Controller--> <!--Window Controller-->
@ -699,11 +701,61 @@
</customView> </customView>
<connections> <connections>
<outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/> <outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/>
<outlet property="noSelectionView" destination="aEc-1k-VmJ" id="KLS-ln-T1K"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="vzM-Vn-mEn" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="vzM-Vn-mEn" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customView id="aEc-1k-VmJ" customClass="NoSelectionView" customModule="Evergreen" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<box boxType="custom" borderType="none" borderWidth="0.0" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="Zb8-hH-gIZ">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<view key="contentView" id="Gmh-qL-lA5">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ECt-Wi-zkb">
<rect key="frame" x="266" y="40" width="109" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No selection" usesSingleLineMode="YES" id="oNn-so-m7W">
<font key="font" metaFont="systemSemibold" size="18"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1AW-Ay-XS8">
<rect key="frame" x="408" y="39" width="154" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Multiple selection" usesSingleLineMode="YES" id="vhD-gM-NUy">
<font key="font" metaFont="systemSemibold" size="18"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="9uN-K3-d35"/>
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QDc-Y3-Nn4"/>
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QV5-qP-Ys6"/>
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="yQr-wJ-pBL"/>
</constraints>
</view>
<color key="borderColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.95999999999999996" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</box>
</subviews>
<constraints>
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="leading" secondItem="aEc-1k-VmJ" secondAttribute="leading" id="5Ay-6o-LZ1"/>
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="top" secondItem="aEc-1k-VmJ" secondAttribute="top" id="Wb7-Zp-hj0"/>
<constraint firstAttribute="trailing" secondItem="Zb8-hH-gIZ" secondAttribute="trailing" id="cYM-up-kUN"/>
<constraint firstAttribute="bottom" secondItem="Zb8-hH-gIZ" secondAttribute="bottom" id="gCy-5R-aK2"/>
</constraints>
<connections>
<outlet property="multipleSelectionLabel" destination="1AW-Ay-XS8" id="gK2-Ya-Dz7"/>
<outlet property="noSelectionLabel" destination="ECt-Wi-zkb" id="Dvq-DU-MxC"/>
</connections>
</customView>
</objects> </objects>
<point key="canvasLocation" x="62" y="774"/> <point key="canvasLocation" x="68" y="946"/>
</scene> </scene>
</scenes> </scenes>
<resources> <resources>

View File

@ -12,14 +12,30 @@ import RSCore
import Data import Data
import RSWeb import RSWeb
final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDelegate { final class DetailViewController: NSViewController, WKUIDelegate {
@IBOutlet var containerView: DetailContainerView! @IBOutlet var containerView: DetailContainerView!
@IBOutlet var noSelectionView: NoSelectionView!
var webview: DetailWebView! var webview: DetailWebView!
var noSelectionView: NoSelectionView!
var article: Article? { var articles: [Article]? {
didSet {
if let articles = articles, articles.count == 1 {
article = articles.first!
return
}
article = nil
if let _ = articles {
noSelectionView.showMultipleSelection()
}
else {
noSelectionView.showNoSelection()
}
}
}
private var article: Article? {
didSet { didSet {
reloadHTML() reloadHTML()
showOrHideWebView() showOrHideWebView()
@ -62,8 +78,6 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.customUserAgent = userAgent webview.customUserAgent = userAgent
} }
noSelectionView = NoSelectionView(frame: self.view.bounds)
containerView.viewController = self containerView.viewController = self
showOrHideWebView() showOrHideWebView()
@ -91,7 +105,7 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.scrollPageDown(sender) webview.scrollPageDown(sender)
} }
// MARK: Notifications // MARK: - Notifications
@objc func timelineSelectionDidChange(_ notification: Notification) { @objc func timelineSelectionDidChange(_ notification: Notification) {
@ -102,8 +116,8 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
return return
} }
let timelineArticle = userInfo[UserInfoKey.article] as? Article let timelineArticles = userInfo[UserInfoKey.articles] as? ArticleArray
article = timelineArticle articles = timelineArticles
} }
func viewWillStartLiveResize() { func viewWillStartLiveResize() {
@ -115,39 +129,11 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil) webview.evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil)
} }
// MARK: Private
private func reloadHTML() {
if let article = article {
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle)
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL)
}
else {
webview.loadHTMLString("", baseURL: nil)
}
} }
private func showOrHideWebView() { // MARK: - WKNavigationDelegate
if let _ = article { extension DetailViewController: WKNavigationDelegate {
switchToView(webview)
}
else {
switchToView(noSelectionView)
}
}
private func switchToView(_ view: NSView) {
if containerView.contentView == view {
return
}
containerView.contentView = view
}
// MARK: WKNavigationDelegate
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
@ -165,6 +151,8 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
} }
} }
// MARK: - WKScriptMessageHandler
extension DetailViewController: WKScriptMessageHandler { extension DetailViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
@ -200,8 +188,39 @@ extension DetailViewController: WKScriptMessageHandler {
} }
} }
// MARK: - Private
private extension DetailViewController { private extension DetailViewController {
func reloadHTML() {
if let article = article {
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle)
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL)
}
else {
webview.loadHTMLString("", baseURL: nil)
}
}
func showOrHideWebView() {
if let _ = article {
switchToView(webview)
}
else {
switchToView(noSelectionView)
}
}
func switchToView(_ view: NSView) {
if containerView.contentView == view {
return
}
containerView.contentView = view
}
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) { func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x"
@ -222,6 +241,8 @@ private extension DetailViewController {
} }
} }
// MARK: -
final class DetailContainerView: NSView { final class DetailContainerView: NSView {
@IBOutlet var detailStatusBarView: DetailStatusBarView! @IBOutlet var detailStatusBarView: DetailStatusBarView!
@ -270,28 +291,28 @@ final class DetailContainerView: NSView {
} }
} }
// MARK: -
final class NoSelectionView: NSView { final class NoSelectionView: NSView {
private var didConfigureLayer = false @IBOutlet var noSelectionLabel: NSTextField!
@IBOutlet var multipleSelectionLabel: NSTextField!
override var wantsUpdateLayer: Bool { func showMultipleSelection() {
return true
noSelectionLabel.isHidden = true
multipleSelectionLabel.isHidden = false
} }
override func updateLayer() { func showNoSelection() {
guard !didConfigureLayer else { noSelectionLabel.isHidden = false
return multipleSelectionLabel.isHidden = true
}
if let layer = layer {
// let color = appDelegate.currentTheme.color(forKey: "MainWindow.Detail.noSelectionView.backgroundColor")
let color = NSColor(calibratedWhite: 0.96, alpha: 1.0)
layer.backgroundColor = color.cgColor
didConfigureLayer = true
}
} }
} }
// MARK: -
private struct ScrollInfo { private struct ScrollInfo {
let contentHeight: CGFloat let contentHeight: CGFloat

View File

@ -110,6 +110,12 @@ extension Array where Element == Article {
return anyArticlePassesTest { !$0.status.starred } return anyArticlePassesTest { !$0.status.starred }
} }
func unreadArticles() -> [Article]? {
let articles = self.filter{ !$0.status.read }
return articles.isEmpty ? nil : articles
}
} }
private extension Array where Element == Article { private extension Array where Element == Article {

View File

@ -516,30 +516,28 @@ extension TimelineViewController: NSTableViewDelegate {
func tableViewSelectionDidChange(_ notification: Notification) { func tableViewSelectionDidChange(_ notification: Notification) {
tableView.redrawGrid() // tableView.redrawGrid()
let selectedRow = tableView.selectedRow if selectedArticles.isEmpty {
if selectedRow < 0 || selectedRow == NSNotFound || tableView.numberOfSelectedRows != 1 {
postTimelineSelectionDidChangeNotification(nil) postTimelineSelectionDidChangeNotification(nil)
return return
} }
if let selectedArticle = articles.articleAtRow(selectedRow) { if selectedArticles.count == 1 {
if (!selectedArticle.status.read) { let article = selectedArticles.first!
markArticles(Set([selectedArticle]), statusKey: .read, flag: true) if !article.status.read {
} markArticles(Set([article]), statusKey: .read, flag: true)
postTimelineSelectionDidChangeNotification(selectedArticle)
}
else {
postTimelineSelectionDidChangeNotification(nil)
} }
} }
private func postTimelineSelectionDidChangeNotification(_ selectedArticle: Article?) { postTimelineSelectionDidChangeNotification(selectedArticles)
}
private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {
var userInfo = UserInfoDictionary() var userInfo = UserInfoDictionary()
if let article = selectedArticle { if let selectedArticles = selectedArticles {
userInfo[UserInfoKey.article] = article userInfo[UserInfoKey.articles] = selectedArticles
} }
userInfo[UserInfoKey.view] = tableView userInfo[UserInfoKey.view] = tableView