Show no-selection or multiple-selection text in the detail view when appropriate.
This commit is contained in:
parent
2bb3d5c6ca
commit
1ba2306b9c
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue