2020-01-11 19:42:28 +00:00
|
|
|
//
|
|
|
|
// 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!
|
2020-04-04 18:35:13 +00:00
|
|
|
@IBOutlet var filterTextView: JavaScriptEditorView!
|
2020-01-11 19:42:28 +00:00
|
|
|
@IBOutlet weak var outlineView: NSOutlineView!
|
|
|
|
@IBOutlet weak var documentCountLabel: NSTextField!
|
|
|
|
|
|
|
|
let mongoController: MongoController
|
|
|
|
let collection: DatabaseCollection
|
|
|
|
|
2020-04-04 18:35:13 +00:00
|
|
|
var defaultFilter: String {
|
|
|
|
"{}"
|
2020-01-11 19:42:28 +00:00
|
|
|
}
|
|
|
|
|
2020-04-04 18:35:13 +00:00
|
|
|
var hasFilterChanged: Bool {
|
|
|
|
return filterTextView.string != defaultFilter
|
2020-01-11 19:42:28 +00:00
|
|
|
}
|
|
|
|
|
2020-02-10 02:13:22 +00:00
|
|
|
var mostRecentQuery: String? = nil
|
|
|
|
|
2020-01-11 19:42:28 +00:00
|
|
|
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()
|
|
|
|
|
2020-02-10 02:13:22 +00:00
|
|
|
refresh()
|
|
|
|
|
2020-01-11 19:42:28 +00:00
|
|
|
verticalSplitView.delegate = self
|
|
|
|
verticalSplitView.setHoldingPriority(.defaultHigh, forSubviewAt: 0)
|
|
|
|
|
2020-04-04 18:35:13 +00:00
|
|
|
filterTextView.isAutomaticQuoteSubstitutionEnabled = false
|
|
|
|
filterTextView.string = defaultFilter
|
2020-04-04 17:25:51 +00:00
|
|
|
|
2020-01-11 19:42:28 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-04-04 17:25:51 +00:00
|
|
|
|
2020-02-10 02:13:22 +00:00
|
|
|
func refresh(reload: Bool = true) {
|
2020-04-04 18:35:13 +00:00
|
|
|
let filterText = filterTextView.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
let filter: Document
|
|
|
|
if !filterText.isEmpty,
|
2020-04-06 23:31:26 +00:00
|
|
|
let doc = ExtendedJSON.toDocument(filterText) {
|
2020-04-04 18:35:13 +00:00
|
|
|
filter = doc
|
2020-02-10 02:13:22 +00:00
|
|
|
} else {
|
2020-04-04 18:35:13 +00:00
|
|
|
filter = [:]
|
2020-02-10 02:13:22 +00:00
|
|
|
}
|
2020-04-04 18:35:13 +00:00
|
|
|
|
|
|
|
let documents = try! mongoController.collection(collection).find(filter).all()
|
|
|
|
rootNodes = documents.map { Node(document: $0) }
|
|
|
|
|
|
|
|
title = "\(self.collection.database).\(self.collection.name)"
|
|
|
|
documentCountLabel.stringValue = "\(documents.count) document\(documents.count == 1 ? "" : "s")"
|
|
|
|
|
2020-02-10 02:13:22 +00:00
|
|
|
if reload {
|
|
|
|
outlineView.reloadData()
|
|
|
|
}
|
2020-07-07 17:23:27 +00:00
|
|
|
|
|
|
|
mongoController.statusManager.set("Queried \(collection)", for: .query, override: true)
|
2020-02-10 02:13:22 +00:00
|
|
|
}
|
|
|
|
|
2020-02-10 02:13:46 +00:00
|
|
|
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
|
|
|
|
}
|
2020-01-12 15:39:24 +00:00
|
|
|
|
2020-02-10 02:13:46 +00:00
|
|
|
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()
|
2020-07-07 17:23:27 +00:00
|
|
|
self.mongoController.statusManager.set("Deleted document", for: .document)
|
2020-02-10 02:13:46 +00:00
|
|
|
case let .failure(error):
|
|
|
|
let alert = NSAlert(error: error)
|
|
|
|
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-12 15:39:24 +00:00
|
|
|
}
|
2020-01-11 19:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@objc func outlineCellDoubleClicked() {
|
|
|
|
if let item = outlineView.item(atRow: outlineView.clickedRow) {
|
|
|
|
if outlineView.isItemExpanded(item) {
|
|
|
|
outlineView.collapseItem(item)
|
|
|
|
} else {
|
|
|
|
outlineView.expandItem(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-10 02:13:46 +00:00
|
|
|
|
|
|
|
@IBAction func deleteNode(_ sender: Any) {
|
|
|
|
guard let node = outlineView.item(atRow: outlineView.clickedRow) as? Node else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if node.parent == nil {
|
|
|
|
deleteRootNode(node)
|
|
|
|
}
|
|
|
|
}
|
2020-01-11 19:42:28 +00:00
|
|
|
|
2020-04-06 23:31:26 +00:00
|
|
|
@IBAction func editDocument(_ sender: Any) {
|
|
|
|
guard let node = outlineView.item(atRow: outlineView.clickedRow) as? Node,
|
|
|
|
node.parent == nil,
|
|
|
|
case let .document(document) = node.value else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let wc = EditDocumentWindowController(mongoController: mongoController, collection: collection, document: document)
|
|
|
|
wc.documentEdited = {
|
|
|
|
self.refresh()
|
2020-07-07 17:23:27 +00:00
|
|
|
self.mongoController.statusManager.set("Updated document", for: .document)
|
2020-04-06 23:31:26 +00:00
|
|
|
}
|
|
|
|
wc.showWindow(nil)
|
|
|
|
}
|
2020-01-11 19:42:28 +00:00
|
|
|
}
|
2020-04-03 02:59:05 +00:00
|
|
|
|
2020-02-10 02:13:22 +00:00
|
|
|
extension QueryViewController: NSMenuItemValidation {
|
|
|
|
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
2020-04-06 23:31:26 +00:00
|
|
|
if menuItem.action == #selector(deleteNode(_:)) || menuItem.action == #selector(editDocument(_:)) {
|
2020-04-04 17:28:25 +00:00
|
|
|
if outlineView.clickedRow != -1, let node = outlineView.item(atRow: outlineView.clickedRow) as? Node, node.parent == nil {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
2020-02-10 02:13:22 +00:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-11 19:42:28 +00:00
|
|
|
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
|
2020-01-12 16:05:01 +00:00
|
|
|
} 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
|
2020-01-11 19:42:28 +00:00
|
|
|
} 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")
|
2020-01-12 16:05:01 +00:00
|
|
|
static let valueTypeColumn = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCol")
|
|
|
|
static let valueTypeCell = NSUserInterfaceItemIdentifier(rawValue: "ValueTypeCell")
|
2020-01-11 19:42:28 +00:00
|
|
|
}
|