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">
<dependencies>
<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="system font weights other than Regular or Bold" minToolsVersion="7.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
@ -699,11 +701,61 @@
</customView>
<connections>
<outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/>
<outlet property="noSelectionView" destination="aEc-1k-VmJ" id="KLS-ln-T1K"/>
</connections>
</viewController>
<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>
<point key="canvasLocation" x="62" y="774"/>
<point key="canvasLocation" x="68" y="946"/>
</scene>
</scenes>
<resources>

View File

@ -12,14 +12,30 @@ import RSCore
import Data
import RSWeb
final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDelegate {
final class DetailViewController: NSViewController, WKUIDelegate {
@IBOutlet var containerView: DetailContainerView!
@IBOutlet var noSelectionView: NoSelectionView!
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 {
reloadHTML()
showOrHideWebView()
@ -62,8 +78,6 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.customUserAgent = userAgent
}
noSelectionView = NoSelectionView(frame: self.view.bounds)
containerView.viewController = self
showOrHideWebView()
@ -91,7 +105,7 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.scrollPageDown(sender)
}
// MARK: Notifications
// MARK: - Notifications
@objc func timelineSelectionDidChange(_ notification: Notification) {
@ -102,8 +116,8 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
return
}
let timelineArticle = userInfo[UserInfoKey.article] as? Article
article = timelineArticle
let timelineArticles = userInfo[UserInfoKey.articles] as? ArticleArray
articles = timelineArticles
}
func viewWillStartLiveResize() {
@ -115,56 +129,30 @@ final class DetailViewController: NSViewController, WKNavigationDelegate, WKUIDe
webview.evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil)
}
// MARK: Private
}
private func reloadHTML() {
// MARK: - WKNavigationDelegate
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)
}
}
extension DetailViewController: WKNavigationDelegate {
private func showOrHideWebView() {
if let _ = article {
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) {
if navigationAction.navigationType == .linkActivated {
if let url = navigationAction.request.url {
Browser.open(url.absoluteString)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
// MARK: - WKScriptMessageHandler
extension DetailViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
@ -200,8 +188,39 @@ extension DetailViewController: WKScriptMessageHandler {
}
}
// MARK: - Private
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) {
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 {
@IBOutlet var detailStatusBarView: DetailStatusBarView!
@ -270,28 +291,28 @@ final class DetailContainerView: NSView {
}
}
// MARK: -
final class NoSelectionView: NSView {
private var didConfigureLayer = false
@IBOutlet var noSelectionLabel: NSTextField!
@IBOutlet var multipleSelectionLabel: NSTextField!
override var wantsUpdateLayer: Bool {
return true
func showMultipleSelection() {
noSelectionLabel.isHidden = true
multipleSelectionLabel.isHidden = false
}
override func updateLayer() {
func showNoSelection() {
guard !didConfigureLayer else {
return
}
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
}
noSelectionLabel.isHidden = false
multipleSelectionLabel.isHidden = true
}
}
// MARK: -
private struct ScrollInfo {
let contentHeight: CGFloat

View File

@ -110,6 +110,12 @@ extension Array where Element == Article {
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 {

View File

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