Make progress on deleting and especially undoing delete.

This commit is contained in:
Brent Simmons 2017-11-04 19:03:47 -07:00
parent 2396a3bfca
commit ef303c0c38
7 changed files with 232 additions and 3 deletions

View File

@ -0,0 +1,134 @@
//
// DeleteFromSidebarCommand.swift
// Evergreen
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSCore
import RSTree
import Account
import Data
final class DeleteFromSidebarCommand: UndoableCommand {
private struct ActionName {
static private let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
static private let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
static private let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
static private let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
static private let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
}
let undoActionName: String
var redoActionName: String {
get {
return undoActionName
}
}
let undoManager: UndoManager
init?(nodesToDelete: [Node], undoManager: UndoManager) {
var numberOfFeeds = 0
var numberOfFolders = 0
for node in nodesToDelete {
if let _ = node.representedObject as? Feed {
numberOfFeeds += 1
}
else if let _ = node.representedObject as? Folder {
numberOfFolders += 1
}
else {
return nil // Delete only Feeds and Folders.
}
}
if numberOfFeeds < 1 && numberOfFolders < 1 {
return nil
}
if numberOfFolders < 1 {
self.undoActionName = numberOfFeeds == 1 ? ActionName.deleteFeed : ActionName.deleteFeeds
}
else if numberOfFeeds < 1 {
self.undoActionName = numberOfFolders == 1 ? ActionName.deleteFolder : ActionName.deleteFolders
}
else {
self.undoActionName = ActionName.deleteFeedsAndFolders
}
self.undoManager = undoManager
}
func perform() {
registerUndo()
}
func undo() {
registerRedo()
}
static func canDelete(_ nodes: [Node]) -> Bool {
// Return true if all nodes are feeds and folders.
// Any other type: return false.
if nodes.isEmpty {
return false
}
for node in nodes {
if let _ = node.representedObject as? Feed {
continue
}
if let _ = node.representedObject as? Folder {
continue
}
return false
}
return true
}
}
// Remember as much as we can now about the items being deleted,
// so they can be restored to the correct place.
private struct SidebarItemSpecifier {
weak var account: Account?
let node: Node
let folder: Folder?
let feed: Feed?
let path: ContainerPath
init?(node: Node) {
self.node = node
var account: Account?
if let feed = node.representedObject as? Feed {
self.feed = feed
account = feed.account
}
else if let folder = node.representedObject as? Folder {
self.folder = folder
account = folder.account
}
guard let account = account else {
return nil
}
self.account = account
self.path = SidebarPath(account: account, node: node)
}
}

View File

@ -82,6 +82,7 @@
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */; };
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */; };
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */; };
84B99C9D1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */; };
84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; };
84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
@ -452,6 +453,7 @@
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTreeControllerDelegate.swift; sourceTree = "<group>"; };
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFolder.swift; sourceTree = "<group>"; };
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFeed.swift; sourceTree = "<group>"; };
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteFromSidebarCommand.swift; sourceTree = "<group>"; };
84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; };
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
@ -542,6 +544,7 @@
isa = PBXGroup;
children = (
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */,
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */,
);
path = Commands;
sourceTree = "<group>";
@ -1237,6 +1240,7 @@
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
84B99C9D1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift in Sources */,
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */,

View File

@ -201,6 +201,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return folder
}
public func ensureFolder(withFolderNames folderNames: [String]) -> Folder? {
// TODO: support subfolders, maybe, some day.
// Since we dont, just take the last name and make sure theres a Folder.
guard let folderName = folderNames.last else {
return nil
}
return ensureFolder(with: folderName)
}
public func canAddFeed(_ feed: Feed, to folder: Folder?) -> Bool {
// If folder is nil, then it should go at the top level.

View File

@ -20,6 +20,7 @@
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77531F6F00E300A165E2 /* AccountManager.swift */; };
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
848935051F62485000CEBD24 /* AccountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935041F62485000CEBD24 /* AccountTests.swift */; };
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; };
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; };
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; };
/* End PBXBuildFile section */
@ -114,6 +115,7 @@
848935041F62485000CEBD24 /* AccountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTests.swift; sourceTree = "<group>"; };
848935061F62485000CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
848935101F62486800CEBD24 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = "<group>"; };
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = "<group>"; };
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -205,6 +207,7 @@
848935101F62486800CEBD24 /* Account.swift */,
841974241F6DDCE4006346C4 /* AccountDelegate.swift */,
841974001F6DD1EC006346C4 /* Folder.swift */,
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */,
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
8419740D1F6DD25F006346C4 /* Container.swift */,
@ -434,6 +437,7 @@
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */,
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,

View File

@ -33,6 +33,7 @@ public protocol Container {
func existingFeed(with feedID: String) -> Feed?
func existingFeed(withURL url: String) -> Feed?
func existingFolder(with name: String) -> Folder?
func existingFolder(withID: Int) -> Folder?
func postChildrenDidChangeNotification()
}
@ -154,6 +155,23 @@ public extension Container {
return nil
}
func existingFolder(withID folderID: Int) -> Folder? {
for child in children {
if let folder = child as? Folder {
if folder.folderID == folderID {
return folder
}
if let subFolder = folder.existingFolder(withID: folderID) {
return subFolder
}
}
}
return nil
}
func postChildrenDidChangeNotification() {
NotificationCenter.default.post(name: .ChildrenDidChange, object: self)

View File

@ -0,0 +1,53 @@
//
// ContainerPath.swift
// Account
//
// Created by Brent Simmons on 11/4/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
// Used to identify the parent of an object.
// Mainly used with deleting objects and undo/redo.
// Especially redo. The idea is to put something back in the right place.
public struct ContainerPath {
private weak var account: Account?
private let names: [String] // empty if top-level of account
private let folderID: Int? // nil if top-level
private let isTopLevel: Bool
// folders should be from top-level down, as in ["Cats", "Tabbies"]
public init(account: Account, folders: [Folder]) {
self.account = account
self.names = folders.map { $0.nameForDisplay }
self.isTopLevel = folders.isEmpty
if let lastFolder = folders.last {
self.folderID = lastFolder.folderID
}
}
public func resolveContainer() -> Container? {
// The only time it should fail is if the account no longer exists.
// Otherwise the worst-case scenario is that it will create Folders if needed.
guard let account = account else {
return nil
}
if isTopLevel {
return account
}
if let folderID = folderID, let folder = account.existingFolder(withID: folderID) {
return folder
}
return account.ensureFolder(withFolderNames: names)
}
}

View File

@ -12,11 +12,12 @@ import RSCore
public final class Folder: DisplayNameProvider, Container, UnreadCountProvider, Hashable {
public weak var account: Account?
public var children = [AnyObject]()
var name: String?
static let untitledName = NSLocalizedString("Untitled ƒ", comment: "Folder name")
public let folderID: Int // not saved: per-run only
static var incrementingID = 0
public let hashValue: Int
// MARK: - Fetching Articles
@ -54,7 +55,11 @@ public final class Folder: DisplayNameProvider, Container, UnreadCountProvider,
self.account = account
self.name = name
self.hashValue = name?.hashValue ?? Folder.untitledName.hashValue
let folderID = Folder.incrementingID
Folder.incrementingID += 1
self.folderID = folderID
self.hashValue = folderID
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
}