A Mac app for working with Mongo databases.
Je kunt niet meer dan 25 onderwerpen selecteren Onderwerpen moeten beginnen met een letter of nummer, kunnen streepjes bevatten ('-') en kunnen maximaal 35 tekens lang zijn.
 

257 regels
8.9 KiB

//
// QueryViewController.swift
// MongoView
//
// Created by Shadowfacts on 1/9/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Cocoa
import MongoSwift
class QueryViewController: NSViewController {
@IBOutlet weak var verticalSplitView: NSSplitView!
@IBOutlet var queryTextView: JavaScriptEditorView!
@IBOutlet weak var outlineView: NSOutlineView!
@IBOutlet weak var documentCountLabel: NSTextField!
let mongoController: MongoController
let collection: DatabaseCollection
var defaultQuery: String {
"db.getCollection('\(collection.name)').find({}).toArray()"
}
var hasQueryChanged: Bool {
return queryTextView.string != defaultQuery
}
var mostRecentQuery: String? = nil
var rootNodes: [Node] = []
init(mongoController: MongoController, collection: DatabaseCollection) {
self.mongoController = mongoController
self.collection = collection
super.init(nibName: "QueryViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
refresh()
verticalSplitView.delegate = self
verticalSplitView.setHoldingPriority(.defaultHigh, forSubviewAt: 0)
queryTextView.font = .monospacedSystemFont(ofSize: 13, weight: .regular)
queryTextView.isAutomaticQuoteSubstitutionEnabled = false
queryTextView.string = defaultQuery
outlineView.dataSource = self
outlineView.delegate = self
outlineView.target = self
outlineView.doubleAction = #selector(outlineCellDoubleClicked)
}
override func viewWillAppear() {
super.viewWillAppear()
verticalSplitView.setPosition(80, ofDividerAt: 0)
}
override func viewDidAppear() {
super.viewDidAppear()
view.window!.makeFirstResponder(outlineView)
}
func refresh(reload: Bool = true) {
if let query = mostRecentQuery {
let connStr = "\(mongoController.connectionString)/\(collection.database)"
rootNodes = MongoEvaluator.eval(command: query, connectingTo: connStr).map {
Node(value: $0)
}
title = query
documentCountLabel.stringValue = "\(rootNodes.count) result\(rootNodes.count == 1 ? "" : "s")"
} else {
let documents = try! mongoController.collection(collection).find().all()
rootNodes = documents.map { Node(document: $0) }
title = "\(self.collection.database).\(self.collection.name)"
documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")"
}
if reload {
outlineView.reloadData()
}
}
func runQuery() {
mostRecentQuery = queryTextView.string
refresh()
}
func deleteRootNode(_ node: Node) {
guard case let .document(doc) = node.value else { return }
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = "Confirm deletion"
alert.informativeText = "Are you sure you want to delete the document"
let id: ObjectId?
if case let .objectId(docId) = doc["_id"] {
id = docId
alert.informativeText += " with id \(docId)"
} else {
id = nil
}
alert.addButton(withTitle: "Delete")
alert.addButton(withTitle: "Cancel")
alert.beginSheetModal(for: view.window!) { (response) in
guard response == .alertFirstButtonReturn else { return }
self.mongoController.collection(self.collection).deleteOne(doc).whenComplete { (result) in
DispatchQueue.main.async {
switch result {
case let .success(result):
guard let result = result, result.deletedCount == 1 else {
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = "Error deleting document"
if let id = id {
alert.informativeText = "The document with id \(id) could not be deleted."
} else {
alert.informativeText = "The document could not be deleted."
}
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
return
}
self.refresh()
case let .failure(error):
let alert = NSAlert(error: error)
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
}
}
}
}
}
@objc func outlineCellDoubleClicked() {
if let item = outlineView.item(atRow: outlineView.clickedRow) {
if outlineView.isItemExpanded(item) {
outlineView.collapseItem(item)
} else {
outlineView.expandItem(item)
}
}
}
@IBAction func deleteNode(_ sender: Any) {
guard let node = outlineView.item(atRow: outlineView.clickedRow) as? Node else {
return
}
if node.parent == nil {
deleteRootNode(node)
}
}
}
extension QueryViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
if menuItem.action == #selector(deleteNode(_:)) {
if outlineView.clickedRow != -1, let node = outlineView.item(atRow: outlineView.clickedRow) as? Node, node.parent == nil {
return true
} else {
return false
}
}
return true
}
}
extension QueryViewController: NSSplitViewDelegate {
func splitView(_ splitView: NSSplitView, constrainSplitPosition proposedPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
return max(80, min(splitView.bounds.height / 2, proposedPosition))
}
}
extension QueryViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
return rootNodes.count
} else if let node = item as? Node {
return node.numberOfChildren
} else {
return 0
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let node = item as? Node {
return node.hasChildren
} else {
return false
}
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
return rootNodes[index]
} else if let node = item as? Node {
return node.children[index]
} else {
fatalError("unreachable")
}
}
}
extension QueryViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let tableColumn = tableColumn,
let node = item as? Node else {
fatalError()
}
if tableColumn.identifier == .fieldNameColumn {
let cell = outlineView.makeView(withIdentifier: .fieldNameCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = node.keyString
cell.textField!.isEditable = false
return cell
} else if tableColumn.identifier == .fieldValueColumn {
let cell = outlineView.makeView(withIdentifier: .fieldValueCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = node.valueString
cell.textField!.isEditable = false
return cell
} else if tableColumn.identifier == .valueTypeColumn {
let cell = outlineView.makeView(withIdentifier: .valueTypeCell, owner: nil) as! NSTableCellView
cell.textField!.stringValue = node.typeString
cell.textField!.isEditable = false
return cell
} else {
return nil
}
}
}
extension NSUserInterfaceItemIdentifier {
static let fieldNameColumn = NSUserInterfaceItemIdentifier(rawValue: "FieldNameCol")
static let fieldValueColumn = NSUserInterfaceItemIdentifier(rawValue: "FieldValueCol")
static let fieldNameCell = NSUserInterfaceItemIdentifier(rawValue: "FieldNameCell")
static let fieldValueCell = NSUserInterfaceItemIdentifier(rawValue: "FieldValueCell")
static let valueTypeColumn = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCol")
static let valueTypeCell = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCell")
}