// // DatabaseViewController.swift // MongoView // // Created by Shadowfacts on 1/10/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import Cocoa import MongoSwift import NIO struct DatabaseCollections { let database: String let collections: [String] } struct DatabaseCollection { let database: String let name: String } class DatabaseViewController: NSViewController { @IBOutlet weak var collectionsOutlineView: NSOutlineView! @IBOutlet weak var detailContainerView: NSView! let mongoController: MongoController private var databaseCollections: [DatabaseCollections] = [] private var selectedCollection: DatabaseCollection? private var placeholderLabel: NSTextField? private var queryViewController: QueryViewController? init(mongoController: MongoController) { self.mongoController = mongoController super.init(nibName: "DatabaseViewController", bundle: .main) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() mongoController.client.listDatabaseNames().flatMap { (databaseNames) -> EventLoopFuture<[DatabaseCollections]> in let futures = databaseNames.map { (name: String) -> EventLoopFuture in let db = self.mongoController.client.db(name) return db.listCollectionNames().map { (collectionNames: [String]) -> DatabaseCollections in let sortedNames = collectionNames.sorted() return DatabaseCollections(database: name, collections: sortedNames) } } return EventLoopFuture.whenAllSucceed(futures, on: futures.first!.eventLoop) }.whenComplete { [weak self] (res) in guard let self = self else { return } switch res { case let .success(databaseCollections): let sortedCollections = databaseCollections.sorted(by: { $0.database < $1.database }) self.databaseCollections = sortedCollections DispatchQueue.main.async { self.collectionsOutlineView.reloadData() } case let .failure(error): fatalError("error getting database names: \(error)") } } collectionsOutlineView.dataSource = self collectionsOutlineView.delegate = self collectionsOutlineView.target = self collectionsOutlineView.doubleAction = #selector(cellDoubleClicked) updateDetailView() } func showCollection(_ collection: DatabaseCollection) { selectedCollection = collection updateDetailView() } private func updateDetailView() { if let collection = selectedCollection { if let placeholderLabel = placeholderLabel { placeholderLabel.removeFromSuperview() } if let oldQueryViewController = self.queryViewController { oldQueryViewController.view.removeFromSuperview() oldQueryViewController.removeFromParent() } let queryViewController = QueryViewController(mongoController: mongoController, collection: collection) self.queryViewController = queryViewController queryViewController.view.translatesAutoresizingMaskIntoConstraints = false addChild(queryViewController) detailContainerView.addSubview(queryViewController.view) NSLayoutConstraint.activate([ queryViewController.view.leadingAnchor.constraint(equalTo: detailContainerView.leadingAnchor), queryViewController.view.trailingAnchor.constraint(equalTo: detailContainerView.trailingAnchor), queryViewController.view.topAnchor.constraint(equalTo: detailContainerView.topAnchor), queryViewController.view.bottomAnchor.constraint(equalTo: detailContainerView.bottomAnchor) ]) self.title = queryViewController.title } else { if let queryViewController = queryViewController { queryViewController.view.removeFromSuperview() queryViewController.removeFromParent() } if self.placeholderLabel == nil { let label = NSTextField(labelWithString: "Select a collection to begin") self.placeholderLabel = label label.translatesAutoresizingMaskIntoConstraints = false detailContainerView.addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: detailContainerView.centerXAnchor), label.centerYAnchor.constraint(equalTo: detailContainerView.centerYAnchor) ]) self.title = "No Query" } } } @objc func cellDoubleClicked() { let item = collectionsOutlineView.item(atRow: collectionsOutlineView.clickedRow)! if item is DatabaseCollections { if collectionsOutlineView.isItemExpanded(item) { collectionsOutlineView.collapseItem(item) } else { collectionsOutlineView.expandItem(item) } } else if let collection = item as? DatabaseCollection { // only open a new window/tab if our own query has changed from the default, otherwise replace our query controller if let queryViewController = queryViewController, queryViewController.hasQueryChanged { (NSApplication.shared.delegate as! AppDelegate).newWindow(mongoController: mongoController, collection: collection) } else { self.selectedCollection = collection updateDetailView() } } } @IBAction func runQuery(_ sender: Any) { queryViewController?.runQuery() } @IBAction func refresh(_ sender: Any) { queryViewController?.refresh() } } extension DatabaseViewController: NSMenuItemValidation { func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { if menuItem.action == #selector(runQuery(_:)) || menuItem.action == #selector(refresh(_:)) { return queryViewController != nil } return true } } extension DatabaseViewController: NSOutlineViewDataSource { func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if item == nil { return databaseCollections.count } else if let database = item as? DatabaseCollections { return database.collections.count } else { return 0 } } func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { return item is DatabaseCollections } func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if item == nil { return databaseCollections[index] } else if let databaseCollections = item as? DatabaseCollections { let collection = databaseCollections.collections[index] return DatabaseCollection(database: databaseCollections.database, name: collection) } else { fatalError() } } } extension DatabaseViewController: NSOutlineViewDelegate { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { if let database = item as? DatabaseCollections { let cell = outlineView.makeView(withIdentifier: .databaseNameCell, owner: nil) as! NSTableCellView cell.textField!.stringValue = database.database cell.textField!.isEditable = false return cell } else if let collection = item as? DatabaseCollection { let cell = outlineView.makeView(withIdentifier: .collectionNameCell, owner: nil) as! NSTableCellView cell.textField!.stringValue = collection.name cell.textField!.isEditable = false return cell } else { fatalError() } } } extension NSUserInterfaceItemIdentifier { static let databaseNameCell = NSUserInterfaceItemIdentifier(rawValue: "DatabaseNameCell") static let collectionNameCell = NSUserInterfaceItemIdentifier(rawValue: "CollectionNameCell") }